JavaTCP Socket实现简单的基于C/S的对聊程序
一、TCP Socket编程的工作流程
Java提供ServerSocket和Socket类实现TCP Socket通信。ServerSocket类提供TCP连接服务;Socket类提供进行通信的Socket对象。
(1)服务器端程序中包含一个提供TCP连接服务的ServerSocket对象和一个参与通信的Socket对象,客户端程序中只包含一个参与通信的Socket对象。
(2)服务器端的ServerSocket对象提供TCP连接服务,连接成功后,实际进行通信的是服务器端的Socket对象和客户端的Socket对象。
(3)在服务器端创建一个 ServerSocket对象,指定端口号, ServerSocket类的accept()方法使服务器处于阻塞状态,等待用户请求;
(4)在客户端创建一个 socket对象,指定主机地址和端口号,连到服务器上;
(5)服务器端接收到客户端的连接请求,建立一条TCP连接,再创建一个Socket对象与客户端的Socekt对象进行通信;
(6)服务器端和客户端分别创建字节输入流和字节输出流,通过字节输入流获得对方发来的数据,通过字节输出流向对方发送数据;
(7)当一方决定结束通信时,向对方发送结束信息;另一方收到结束信息后,双方分别关闭各自的TCP连接;ServerSocket对象停止等待客户端的连接请求。
(图片来源网络,侵权联系删除)
二、使用TCP Socket设计一个简单的基于C/S的对聊程序。
1、客户端
代码如下:
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class client {
public static void main(String[] args) {
try{
Socket client=new Socket("127.0.0.1",5555);
Scanner in=new Scanner(client.getInputStream());
System.out.println(in.nextLine());
PrintWriter pw=new PrintWriter(client.getOutputStream());
System.out.println("客户端1:");
Scanner out=new Scanner(System.in);//用来读取键盘输入
while(out.hasNextLine()){
String data=out.nextLine();
pw.println("客户端1:"+data);//将内容写到Socket
pw.flush();//将缓冲区内容强制写入目标设备
String indata=in.nextLine();//等待服务器发送内容。利用Scanner没有获取到服务器的数据则将线程阻塞。
if(indata.equals("break")) break;
System.out.println("服务器:"+indata);
System.out.println("客户端1:");
}
in.close();
pw.close();
out.close();
client.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
2、服务器
代码如下:
package shiyan8;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class server {
public static void main(String[] args) throws IOException {
try{
System.out.println("等待客户端连接……");
ServerSocket server=new ServerSocket(6666);
Socket socket=server.accept();
PrintWriter pw=new PrintWriter(socket.getOutputStream());
pw.println("连接成功!请您先发言!");
pw.flush();
Scanner in=new Scanner(socket.getInputStream());//客户端的数据输入流
while(in.hasNextLine()){
String indata=in.next();//取客户端发送的内容
System.out.println(indata);
System.out.println("服务器:");
Scanner outdata=new Scanner(System.in);//服务器发送给客户端的数据输出流
String out=outdata.next();
if(out.equals("break")) break;
pw.println(out);//将内容写到socket中
pw.flush();
}
in.close();
pw.close();
socket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
3、测试结果
客户端连接上服务器后,服务器先发送连接成功的消息,让客户端先发送消息,服务器收到消息后,在输入消息回复给客户端。这样就实现了简单的聊天。
(1)客户端
(2)服务器
三、利用多线程扩展,实现一对多的对聊程序
1、客户端1、客户端2
代码如下:
//客户端1
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class client {
public static void main(String[] args) {
try{
Socket client=new Socket("127.0.0.1",5555);
Scanner in=new Scanner(client.getInputStream());
System.out.println(in.nextLine());
PrintWriter pw=new PrintWriter(client.getOutputStream());
System.out.println("客户端1:");
Scanner out=new Scanner(System.in);//用来读取键盘输入
while(out.hasNextLine()){
String data=out.nextLine();
pw.println("客户端1:"+data);//将内容写到Socket
pw.flush();//将缓冲区内容强制写入目标设备
String indata=in.nextLine();//等待服务器发送内容。利用Scanner没有获取到服务器的数据则将线程阻塞。
if(indata.equals("break")) break;
System.out.println("服务器:"+indata);
System.out.println("客户端1:");
}
in.close();
pw.close();
out.close();
client.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
//客户端2
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class client1 {
public static void main(String[] args) {
try{
Socket client=new Socket("127.0.0.1",5555);
Scanner in=new Scanner(client.getInputStream());
System.out.println(in.nextLine());
PrintWriter pw=new PrintWriter(client.getOutputStream());
System.out.println("客户端2:");
Scanner out=new Scanner(System.in);//用来读取键盘输入
while(out.hasNextLine()){
String data=out.nextLine();
pw.println("客户端2:"+data);//将内容写到Socket
pw.flush();//将缓冲区内容强制写入目标设备
String indata=in.nextLine();//等待服务器发送内容。利用Scanner没有获取到服务器的数据则将线程阻塞。
if(indata.equals("break")) break;
System.out.println("服务器:"+indata);
System.out.println("客户端2:");
}
in.close();
pw.close();
out.close();
client.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
2、多线程服务器
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class multiServer {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(4);
ServerSocket server=new ServerSocket(5555);
while(true){
try{
Socket socket=server.accept();
//主线程获取客户端连接Thread
executor.submit(new Processor(socket));
}catch(Exception e){
e.printStackTrace();
}
}
}
}
class Processor implements Runnable{
private Socket socket;
public Processor(Socket socket){
this.socket=socket;
}
public void run(){
try{
PrintWriter pw=new PrintWriter(socket.getOutputStream());
pw.println("连接成功!请您先发言!");
pw.flush();
Scanner in=new Scanner(socket.getInputStream());//客户端的数据输入流
while(in.hasNextLine()){
String indata=in.next();//取客户端发送的内容
System.out.println(indata);
System.out.println("服务器:");
Scanner outdata=new Scanner(System.in);//服务器发送给客户端的数据输出流
String out=outdata.next();
pw.println(out);//将内容写到socket中
pw.flush();
}
Thread.sleep(10);
if(socket!=null)socket.close();
}catch(IOException e){
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3、测试结果
(1)客户端1
(2)客户端2
(3)多线程服务器
总结
客户端和服务器的通信,socket通过Ip地址、端口号实现客户端和服务器的数据传输。连接建立以后,可以通过OutputStream和InputStream进行通信,关闭连接时,先关闭其他所有的输入输出流,最后关闭socket。实现一对多的通信时,利用了线程池的概念,让服务器一直监听客户端的连接。之后利用客户端的socket创建一个Process进程,利用submit方法提交个线程池运行。实现Runable接口的run方法与之前编写一对一服务器的思路一致。
不足:一对多服务器客户端的实现过程还不是很完善,还存在一些问题。例如:客户端1和客户端2都发送给服务器时,服务器没办法选择将消息发送给指定的客户端。不符合实际的需求。采用Scanner阻塞客户端线程的方法,代表了服务器在要发送给两个客户端消息的时候,是没办法知道消息发送给了那个客户端,可能是客户端1也可能是客户端2。这取决于服务器发送之后,是客户端1还是客户端2抢占了CPU的资源。谁先抢到就收到这个消息。这也不符合实际的需求。
希望大佬能够帮助解决这个问题,感激不尽。