socket编程
1. 新建java项目, TCPSocket,包tcp, 类ServerCode.java, ClientCode.java
服务器:
1. 在服务器,用一个端口来实例化一个 ServerSocket对象。此时,服务器就可以这个端口时刻监听从客户端发来的连接请求。
2.调用ServerSocket的accept方法,开始监听连接从端口上发来的连接请求。
3.利用accept方法返回的客户端的Socket对象,进行读写IO的操作
通讯完成后,关闭打开的流和Socket对象。
package 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; 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(); } } }
这段代码的主要业务逻辑是:
1. 在上述代码里的main函数前,我们设置了通讯所用到的端口号,为3333。
2. 在main函数里,根据给定3333端口号,初始化一个ServerSocket对象s,该对象用来承担服务器端监听连接和提供通讯服务的功能。
3. 调用ServerSocket对象的accept方法,监听从客户端的连接请求。当完成调用accept方法后,整段服务器端代码将回阻塞在这里,直到客户端发来connect请求。
4. 当客户端发来connect请求,或是通过构造函数直接把客户端的Socket对象连接到服务器端后,阻塞于此的代码将会继续运行。此时服务器端将会根据accept方法的执行结果,用一个Socket对象来描述客户端的连接句柄。
5. 创建两个名为in和out的对象,用来传输和接收通讯时的数据流。
6. 创建一个while(true)的死循环,在这个循环里,通过in.readLine()方法,读取从客户端发送来的IO流(字符串),并打印出来。如果读到的字符串是“byebye”,那么退出while循环。
7. 在try…catch…finally语句段里,不论在try语句段里是否发生异常,并且不论这些异常的种类,finally从句都将会被执行到。在finally从句里,将关闭描述客户端的连接句柄socket对象和ServerSocket类型的s对象。
客户端:
1.用服务器的IP地址和端口号实例化Socket对象。
2.调用connect方法,连接到服务器上。
3.将发送到服务器的IO流填充到IO对象里,比如BufferedReader/PrintWriter。
4.利用Socket提供的getInputStream和getOutputStream方法,通过IO流对象,向服务器发送数据流。
5. 通讯完成后,关闭打开的IO对象和Socket。
ClientCode.java:
package 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; 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(); } } }
上述客户端代码的主要业务逻辑是:
1. 同样定义了通讯端口号,这里给出的端口号必须要和服务器端的一致。
2. 在main函数里,根据地址信息“localhost”,创建一个InetAddress类型的对象addr。这里,因为我们把客户端和服务器端的代码都放在本机运行,所以同样可以用“127.0.0.1”字符串,来创建InetAddress对象。
3. 根据addr和端口号信息,创建一个Socket类型对象,该对象用来同服务器端的ServerSocket类型对象交互,共同完成C/S通讯流程。
4. 同样地创建in和out两类IO句柄,用来向服务器端发送和接收数据流。
5. 通过out对象,向服务器端发送"Hello Server,I am …"的字符串。发送后,同样可以用in句柄,接收从服务器端的消息。
6. 利用out对象,发送”byebye”字符串,用以告之服务器端,本次通讯结束。
7. 在finally从句里,关闭Socket对象,断开同服务器端的连接。
运行server显示:
The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=3333]
运行client显示:
socket = Socket[addr=localhost/127.0.0.1,port=3333,localport=14404] Hello Server,I am Mike close the Client socket and the io.
然后server会显示:
The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=3333] Accept the Client: Socket[addr=/127.0.0.1,port=14404,localport=3333] In Server reveived the info: Hello Server,I am Mike close the Server socket and the io.
- 多线程链接多个client:
编写线程代码的主体执行类ServerThreadCode:
package tcp; import java.io.*; import java.net.*; 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(); } } } }
这个类的业务逻辑说明如下:
1.这个类通过继承Thread类来实现线程的功能,也就是说,在其中的run方法里,定义了该线程启动后要执行的业务动作。
2. 这个类提供了两种类型的重载函数。在参数类型为Socket的构造函数里, 通过参数,初始化了本类里的Socket对象,同时实例化了两类IO对象。在此基础上,通过start方法,启动定义在run方法内的本线程的业务逻辑。
3. 在定义线程主体动作的run方法里,通过一个for(;;)类型的循环,根据IO句柄,读取从Socket信道上传输过来的客户端发送的通讯信息。如果得到的信息为“byebye”,则表明本次通讯结束,退出for循环。
4. catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。
上述的线程主体代码将会在ThreadServer类里被调用。
第三步,编写服务器端的主体类ThreadServer,代码如下所示:
package tcp; import java.io.*; import java.net.*; 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(); } } }
这段代码的主要业务逻辑说明如下:
1. 首先定义了通讯所用的端口号,为3333。
2. 在main函数里,根据端口号,创建一个ServerSocket类型的服务器端的Socket,用来同客户端通讯
3. 在for(;;)的循环里,调用accept方法,监听从客户端请求过来的socket,请注意这里又是一个阻塞。当客户端有请求过来时,将通过ServerThreadCode的构造函数,创建一个线程类,用来接收客户端发送来的字符串。在这里我们可以再一次观察ServerThreadCode类,在其中,这个类通过构造函数里的start方法,开启run方法,而在run方法里,是通过sin对象来接收字符串,通过sout对象来输出。
4. 在finally从句里,关闭服务器端的Socket,从而结束本次通讯。
编写的基于多线程的客户端代码,新建一个名为ThreadClient.java的代码文件
编写客户端的主体代码,在这段代码里,将通过for循环,根据指定的待创建的线程数量,通过ClientThreadCode的构造函数,创建若干个客户端线程,同步地和服务器端通讯。
package tcp; import java.net.*; import java.io.*; 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); } } }
这段代码执行以后,在客户端将会有3个通讯线程,每个线程首先将先向服务器端发送"Hello Server,My id is "的字符串,然后发送”byebye”,终止该线程的通讯
编写线程执行主体的ClientThreadCode类,同样,这个类通过继承Thread来实现线程的功能
package tcp; import java.net.*; import java.io.*;
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(); } } } }
这个类的主要业务逻辑是:
1. 在构造函数里, 通过参数类型为InetAddress类型参数和3333,初始化了本类里的Socket对象,随后实例化了两类IO对象,并通过start方法,启动定义在run方法内的本线程的业务逻辑。
2. 在定义线程主体动作的run方法里,通过IO句柄,向Socket信道上传输本客户端的ID号,发送完毕后,传输”byebye”字符串,向服务器端表示本线程的通讯结束。
3. 同样地,catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。
运行效果:
server显示:
The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=3333]
client显示:
Hello Server,My id is 0 Hello Server,My id is 2 Hello Server,My id is 1
server再显示:
The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=3333] In Server reveived the info: Hello Server,My id is 1 In Server reveived the info: Hello Server,My id is 0 In Server reveived the info: Hello Server,My id is 2 closing the server socket! close the Server socket and the io. closing the server socket! close the Server socket and the io. closing the server socket! close the Server socket and the io.
- UDP协议与传输数据报文:
设计ClientBean这个类,在其中封装了客户端通讯的一些功能方法,在此基础上,通过UDPClient.java文件,实现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; //以下是各属性的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; } 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)); } }
在上述的代码里,我们定义了描述用来实现UDP通讯的DatagramSocket类型对象ds,描述客户端和服务器端的端口号clientport和serverport,用于描述通讯信息的buffer和content对象,其中,buffer对象是byte数组类型的,可通过UDP的数据报文传输,而content是String类型的,在应用层面表示用户之间的通讯内容,另外还定义了InetAddress类型的ia变量,用来封装通讯地址信息
开发设计UDPClient这个类:
这段代码的主要业务逻辑是,首先初始化了BufferedReader类型的br对象,该对象可以接收从键盘输入的字符串。随后启动一个while(true)的循环,在这个循环体里,接收用户从键盘的输入,如果用户输入的字符串不是“end”,或不是为空,则开启一个UDPClient类型的线程,并通过定义在run方法里的线程主体动作,发送接收到的消息。如果在循环体里,接收到“end”或空字符,则通过break语句,退出循环。
从上述代码里,我们可以看出,对于每次UDP发送请求,UDPClient类都将会启动一个线程来发送消息。
UDPClient.java:
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 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(); } } public void run(){ try{ client.setContent(content); client.sendToServer(); } catch(Exception ex){ System.err.println(ex.getMessage()); } }//end of run //main 方法 //… }
设计ServerBean类的代码
在UDP的服务器端里,主要通过ServerBean类里提供的listenClient方法,监听从客户端发送过来的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 { private DatagramSocket ds; //用来封装通讯字符串 private byte buffer[]; //客户端的端口号 private int clientport ; //服务器端的端口号 private int serverport; //通讯内容 private String content; //描述通讯地址 private InetAddress ia; //以下是各属性的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; } 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); } }
而UDP通讯的服务器端代码相对简单,以下是UDPServer类的全部代码
在UDP的服务器端里,主要通过ServerBean类里提供的listenClient方法,监听从客户端发送过来的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(); } }