使用套接字实现基于 TCP 协议的服务器和客户机程序

依据 TCP 协议,在 C/S 架构的通讯过程中,客户端和服务器的 Socket 动作如下:

客户端:

1.用服务器的 IP 地址和端口号实例化 Socket 对象。

2.调用 connect 方法,连接到服务器上。

3.将发送到服务器的 IO 流填充到 IO 对象里,比如 BufferedReader/PrintWriter。

4.利用 Socket 提供的 getInputStream 和 getOutputStream 方法,通过 IO 流对象,向服务器发送数据流。

5. 通讯完成后,关闭打开的 IO 对象和 Socket。

服务器:

1. 在服务器,用一个端口来实例化一个 ServerSocket 对象。此时,服务器就可以这个端口时刻监听从客户端发来的连接请求。

2.调用 ServerSocket 的 accept 方法,开始监听连接从端口上发来的连接请求。

3.利用 accept 方法返回的客户端的 Socket 对象,进行读写 IO 的操作通讯完成后,关闭打开的流和 Socket 对象。


下面是一个简单的客户端与服务器端的例子:

客户端:

package my.socket.tcp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
上述客户端代码的主要业务逻辑是:
* @author asus
*
*/
public class ClientCode {
static String clientName = "Mike";
// 端口号
public static int portNo = 3333;
public static void main(String[] args) throws IOException {
// 设置连接地址类,连接本地
InetAddress addr = InetAddress.getByName("localhost");
// 要对应服务器端的 3333 端口号
Socket socket = new Socket(addr, portNo);
try {
System.out.println("socket = " + socket);
// 设置 IO 句柄
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),
true);
out.println("Hello Server,I am " + clientName);
String str = in.readLine();
System.out.println(str);
out.println("byebye");
} finally {
System.out.println("close the Client socket and the io.");
socket.close();
}
}
}

服务器端:

package my.socket.tcp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
*
编写服务器端的主体代码:
这段代码的主要业务逻辑是:
* @author asus
*
*/
public class ServerCode {
// 设置端口号
public static int portNo = 3333;
public static void main(String[] args) throws IOException {
ServerSocket s = new ServerSocket(portNo);
System.out.println("The Server is start: " + s);
// 阻塞,直到有客户端连接
Socket socket = s.accept();
try {
System.out.println("Accept the Client: " + socket);
// 设置 IO 句柄
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),
true);
while (true) {
String str = in.readLine();
if (str.equals("byebye")) {
break;
}
System.out.println("In Server reveived the info: " + str);
out.println(str);
}
} finally {
System.out.println("close the Server socket and the io.");
socket.close();
s.close();
}
}
}

先运行服务器端,再运行客户端之后,可以看到服务器端接收到来自客户端发送的信息。


通常网络编程都是用多线程来实现,将大大地提高服务器端的利用效率,并能使服务器端能具备完善的

服务功能。

package my.socket.tcp2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
* 这个类的主要业务逻辑是:
里的 Socket 对象,随后实例化了两类 IO 对象,并通过 start 方法,启动定义在 run 方法内的
本线程的业务逻辑。
端的 ID 号,发送完毕后,传输”byebye”字符串,向服务器端表示本线程的通讯结束。
里,将在通讯结束后关闭客户端的 Socket 句柄。
* @author asus
*
*/
class ClientThreadCode extends Thread {
// 客户端的 socket
private Socket socket;
// 线程统计数,用来给线程编号
private static int cnt = 0;
private int clientId = cnt++;
private BufferedReader in;
private PrintWriter out;
// 构造函数
public ClientThreadCode(InetAddress addr) {
try {
socket = new Socket(addr, 3333);
} catch (IOException e) {
e.printStackTrace();
}
// 实例化 IO 对象
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
// 开启线程
start();
} catch (IOException e) {
// 出现异常,关闭 socket
try {
socket.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
// 线程主体方法
public void run() {
try {
out.println("Hello Server,My id is " + clientId);
String str = in.readLine();
System.out.println(str);
out.println("byebye");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package my.socket.tcp2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
/**
这个类的业务逻辑说明如下:
* @author asus
*
*/
public class ServerThreadCode extends Thread {
// 客户端的 socket
private Socket clientSocket;
// IO 句柄
private BufferedReader sin;
private PrintWriter sout;
// 默认的构造函数
public ServerThreadCode() {
}
public ServerThreadCode(Socket s) throws IOException {
clientSocket = s;
// 初始化 sin 和 sout 的句柄
sin = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
sout = new PrintWriter(new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())), true);
// 开启线程
start();
}
// 线程执行的主体函数
public void run() {
try {
// 用循环来监听通讯内容
for (;;) {
String str = sin.readLine();
// 如果接收到的是 byebye,退出本次通讯
if (str.equals("byebye")) {
break;
}
System.out.println("In Server reveived the info: " + str);
sout.println(str);
}
System.out.println("closing the server socket!");
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("close the Server socket and the io.");
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package my.socket.tcp2;
import java.io.IOException;
import java.net.InetAddress;
/**
* 这段代码执行以后,在客户端将会有 3 个通讯线程,每个线程首先将先向服务器端发送"Hello
Server,My id is "的字符串,然后发送”byebye”,终止该线程的通讯。
* @author asus
*
*/
public class ThreadClient {
public static void main(String[] args) throws IOException, InterruptedException {
int threadNo = 0;
InetAddress addr = InetAddress.getByName("localhost");
for (threadNo = 0; threadNo < 3; threadNo++) {
new ClientThreadCode(addr);
}
}
}
package my.socket.tcp2;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
这段代码的主要业务逻辑说明如下:
用来同客户端通讯。
这里又是一个阻塞。当客户端有请求过来时,将通过 ServerThreadCode 的构造函数,创建一
个线程类,用来接收客户端发送来的字符串。在这里我们可以再一次观察 ServerThreadCode
类,在其中,这个类通过构造函数里的 start 方法,开启 run 方法,而在 run 方法里,是通过
sin 对象来接收字符串,通过 sout 对象来输出。
* @author asus
*
*/
public class ThreadServer {
// 端口号
static final int portNo = 3333;
public static void main(String[] args) throws IOException {
// 服务器端的 socket
ServerSocket s = new ServerSocket(portNo);
System.out.println("The Server is start: " + s);
try {
for (;;) {
// 阻塞,直到有客户端连接
Socket socket = s.accept();
// 通过构造函数,启动线程
new ServerThreadCode(socket);
}
} finally {
s.close();
}
}
}

首先运行服务器端,再运行客户端,可以清楚的看到服务器端多线程接收到来自客户端的消息。


下面是同时开启服务器端和客户端,两者进行不间断的通信。

package my.socket.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class ClientBean {
// 描述 UDP 通讯的 DatagramSocket 对象
private DatagramSocket ds;
// 用来封装通讯字符串
private byte buffer[];
// 客户端的端口号
private int clientport;
// 服务器端的端口号
private int serverport;
// 通讯内容
private String content;
// 描述通讯地址
private InetAddress ia;
public ClientBean() throws SocketException, UnknownHostException {
buffer = new byte[1024];
clientport = 1985;
serverport = 1986;
content = "";
ds = new DatagramSocket(clientport);
ia = InetAddress.getByName("localhost");
}
public void sendToServer() throws IOException {
buffer = content.getBytes();
ds.send(new DatagramPacket(buffer, content.length(), ia, serverport));
}
// 以下是各属性的 Get 和 Set 类型方法
public byte[] getBuffer() {
return buffer;
}
public void setBuffer(byte[] buffer) {
this.buffer = buffer;
}
public int getClientport() {
return clientport;
}
public void setClientport(int clientport) {
this.clientport = clientport;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public DatagramSocket getDs() {
return ds;
}
public void setDs(DatagramSocket ds) {
this.ds = ds;
}
public InetAddress getIa() {
return ia;
}
public void setIa(InetAddress ia) {
this.ia = ia;
}
public int getServerport() {
return serverport;
}
public void setServerport(int serverport) {
this.serverport = serverport;
}
}
package my.socket.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class ServerBean {
// 描述 UDP 通讯的 DatagramSocket 对象
private DatagramSocket ds;
// 用来封装通讯字符串
private byte buffer[];
// 客户端的端口号
private int clientport;
// 服务器端的端口号
private int serverport;
// 通讯内容
private String content;
// 描述通讯地址
private InetAddress ia;
public ServerBean() throws SocketException, UnknownHostException {
buffer = new byte[1024];
clientport = 1985;
serverport = 1986;
content = "";
ds = new DatagramSocket(serverport);
ia = InetAddress.getByName("localhost");
}
public void listenClient() throws IOException {
// 在循环体里接收消息
while (true) {
// 初始化 DatagramPacket 类型的变量
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
// 接收消息,并把消息通过 dp 参数返回
ds.receive(dp);
content = new String(dp.getData(), 0, dp.getLength());
// 打印消息
print();
}
}
public void print() {
System.out.println(content);
}
public DatagramSocket getDs() {
return ds;
}
public void setDs(DatagramSocket ds) {
this.ds = ds;
}
public byte[] getBuffer() {
return buffer;
}
public void setBuffer(byte[] buffer) {
this.buffer = buffer;
}
public int getClientport() {
return clientport;
}
public void setClientport(int clientport) {
this.clientport = clientport;
}
public int getServerport() {
return serverport;
}
public void setServerport(int serverport) {
this.serverport = serverport;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public InetAddress getIa() {
return ia;
}
public void setIa(InetAddress ia) {
this.ia = ia;
}

}
package my.socket.udp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class UDPClient implements Runnable {
public static String content;
public static ClientBean client;
public void run() {
try {
client.setContent(content);
client.sendToServer();
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}// end of run
// main 方法
// …
public static void main(String args[]) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
client = new ClientBean();
System.out.println("客户端启动...");
while (true) {
// 接收用户输入
content = br.readLine();
// 如果是 end 或空,退出循环
if (content == null || content.equalsIgnoreCase("end") || content.equalsIgnoreCase("")) {
break;
}
// 开启新线程,发送消息
new Thread(new UDPClient()).start();
}
}
}
package my.socket.udp;
import java.io.IOException;
public class UDPServer {
public static void main(String args[]) throws IOException {
System.out.println("服务器端启动...");
// 初始化 ServerBean 对象
ServerBean server = new ServerBean();
// 开启监听程序
server.listenClient();
}
}

先运行服务器端,再运行客户端。

在客户端输入想要发送的字符,在服务器端可以接收到。