您的位置:首页 > 理论基础 > 计算机网络

Java网络编程实践和总结 --- 基于TCP的Socket编程之echo回显的操作

2015-03-27 19:55 691 查看
最近在学习Java的网络,在快速过完基础知识以后,接下来的一系列文章将会通过编写一些简单的实例Demo来总结和巩固Java网络编程的知识。

首先要讲的是基于TCP的Socket编程:

数据在Internet中是以有限大小的包形式进行传输的,这些数据包成为数据包(datagram)。每个数据报包都包含其首部(header)和有效载荷(payload)即数据要传输数据的大小。首部包含要去往的地址和端口号,以及来自哪里的地址和端口。基于TCP的Socket是稳定的面向连接的网络传输,再数据开始传输时首先会进行三次握手的操作,已保证连接成功。当一个socket在两台主机之间建立连接以后数据包就会根据TCP/IP协议等进行层层的封装,每经过一层网络协议就会在数据头前添加该协议的包头,这样数据包就会通过协议栈的封包和拆包准确到达目的主机。数据包都会标记各自的发送接收顺序,即使因为由于网络的原因导致接受方已不同于发送时的顺序收到数据包,最终都会根据序号重新排列,组成顺序完成的数据,如果中途的数据包丢失,就会重新发送。socket就通过这方式来实现主机之间的可靠的连接通信。

Socket基础:socket是两台主机之间的一个连接,根据字面上翻译理解socket为套接字,套接字主要包括两个要素:即IP地址以及端口,通过这两个要素就可以在网络中找到任意的一个主机进行通信。

Socket主要进行七项基本的操作:

1.连接远程主机

2.发送数据

3.接受数据

4.关闭连接

5.绑定端口

6.监听入站数据

7.在绑定的端口上接受来自远程主机的连接

Java的Socket类就是用于实现以上的功能,前三项操作可用于客户端和服务端,后三项操作则只有服务端才需要使用。

Socket类主要用于客户端和服务端,ServerSocket类则只用于服务端。

接下来我们同时编写以下客户端和服务端实例来说明Socket和SeverSocket的使用:

实例一:编译客户端,实现一个echo回显的操作,客户端发送信息给服务端,服务端接受到以后将内容转化为大写后发回给客户端显示。服务端处理方式对每一个请求会启动一个新的线程来单独为客户端服务。

以下为客户端代码:

package com.javanet.demo1;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;

public class EchoClient {

//地址使用当前主机测试
private static String defaultHost = "localhost";
//服务器端口默认使用12345
private static int defaultPort = 12345;
//echo测试的结束标志
private static final String END_FLAG = "over";
//用于连接服务器的客户端Socket
private Socket connection;

public EchoClient() throws UnknownHostException, IOException {
//连接服务器服务
connection = new Socket(defaultHost, defaultPort);
}

public static void main(String[] args) {
// TODO Auto-generated method stub

try {
/**
* 	EchoClient客户端开启两个线程分别完成不同的工作:
* 	读取线程ReadThread用于从标准输入当中读取字符串,并且把字符串写到服务器端
* 	写入线程WriteThread用于从服务器端接受Echo字符串
* */
EchoClient echoClient = new EchoClient();
ReadThread readThread = echoClient.new ReadThread();
WriteThread writThread = echoClient.new WriteThread();
//开启线程
writThread.start();
readThread.start();
//等待线程结束
writThread.join();
readThread.join();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

//读取线程ReadThread用于从标准输入当中读取字符串,并且把字符串写到服务器端
private class ReadThread extends Thread {

@Override
public void run() {
// TODO Auto-generated method stub
BufferedInputStream bis = null;
byte[] buf = new byte[1024];
int readByte = -1;
String content = null;
try {
//通过客户端Socket获取和服务端通信的InputStream
bis = new BufferedInputStream(connection.getInputStream());
//开始循环往服务器端写入需要处理的Echo数据
while (true) {
/**
* 从服务器的读取流中读取数据,
* 如果服务器没有返回往流中写入数据,则客户端无法读取数据,会一直阻塞等待
*/
readByte = bis.read(buf);

content = new String(buf, 0, readByte);
System.out.println("<=== Server Echo : " + content);
/**
* 客户端如果读到返回的字符串为over就结束客户端
*/
if (content.equalsIgnoreCase(END_FLAG)) {
System.out.println(">>> Echo End ! <<<");
break;
}
}
} catch (IOException e) {
/**
* 当服务器接受到结束标志符时,END_FLAG标识客户端需要关闭服务,服务端的写入流将会关闭,
* 而以上while循环中客户端的读取流还在读取数据时就会抛出异常,此时说明服务结束
*/
//System.out.println("Server Write Thread is closed, the client Read Thread closed too");
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}

//写入线程WriteThread用于从服务器端接受Echo字符串
private class WriteThread extends Thread {

@Override
public void run() {
// TODO Auto-generated method stub

BufferedReader br = null;
BufferedOutputStream bos = null;
String content = null;
byte[] buf = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
bos = new BufferedOutputStream(connection.getOutputStream());
//从标准输入中获取字符串,再写到服务器的流中
while (true) {
System.out.println("===> Please input :");
//从标准输入中读取,会阻塞
content = br.readLine();
buf = content.getBytes();
//往服务器读取流中写入数据
bos.write(buf, 0, buf.length);
bos.flush();
//判断是否为结束标识符,结束写入线程
if (content.equalsIgnoreCase(END_FLAG)) {
//System.out.println("---> Client echo end!");
break;
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

}

}


以下为服务端代码:

package com.javanet.demo1;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class EchoServer {

//服务端的默认端口
private static int defaultPort = 12345;
//用于接受入站的客户端
private Socket client;
//用于创建服务并且监听指定端口
private ServerSocket server;
public static final String END_FLAG = "over";

public static void main(String[] args) {
try {
EchoServer echoServer = new EchoServer();
/**
* 	ResponseThread用于响应入站的客户端请求的线程
* 	一旦有入站请求,将获取其客户端的Socket并为没一个入站的请求启动新的线程单独处理。
* */
ResponseThread responseThread = echoServer.new ResponseThread();
//启动处理入站请求的线程
responseThread.start();
//等待线程
responseThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

public EchoServer() throws IOException {
//新建服务端的Socket
server = new ServerSocket(defaultPort);
}

private class ResponseThread extends Thread {

@Override
public void run() {

while (true) {
try {
//不停的监听入站请求,会阻塞
client = server.accept();
//一旦获取到入站的客户端请求则新建HandlerThread线程来单独处理每一个请求
System.out.println("accpet from client : " + client.getInetAddress() + " , port : " + client.getPort());
Thread readThread = new HandlerThread(client);
//启动处理线程
readThread.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}
}

//用于处理请求的线程
private class HandlerThread extends Thread {
//获取客户端的Socket
private Socket client;

public HandlerThread(Socket client) {
this.client = client;
}

@Override
public void run() {
BufferedInputStream bis = null;
BufferedWriter bw = null;
try {
//读取和写入流的初始化
bis = new BufferedInputStream(client.getInputStream());
bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
String readString = null;
String writeString = null;
byte[] buf = new byte[1024];
int len = -1;
while (true) {
//从流中获取客户端写入的信息,会阻塞等待
len = bis.read(buf);
readString = new String(buf, 0, len);
writeString = readString.toUpperCase();
//判断是否结束当前服务
if (readString.equalsIgnoreCase(END_FLAG)) {
break;
}
bw.write(writeString);
bw.flush();
}
//结束Echo信息的发送,将关闭写入流,客户端的读取将会抛出异常结束
System.out.println("client end client : "  + client.getInetAddress() + " , port : " + client.getPort());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

}

}


首先启动服务端,再启动一个或多个客户端测试

测试结果:

服务端输出结果:

accpet from client : /127.0.0.1 , port : 53773

client end client : /127.0.0.1 , port : 53773

客户端输出结果:

===> Please input :

hello

===> Please input :

<=== Server Echo : HELLO

world

===> Please input :

<=== Server Echo : WORLD

over

以上结果显示:当客户端启动后,服务端接受到连接打印出客户端的IP以及端口号

当客户端输入hello,服务端回显大写

当客户端输入over时结束,服务端打印结束的客户端IP以及端口号
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: