网络通信是服务器与客户端之间的数据传输。
客户端连接服务器时需要知道服务器的IP(哪一台电脑)和端口(电脑上具体的应用)。
在Java中,网路传输服务器需要干的事情有:
1.建立基站,表示通过服务器的端口通信:
创建绑定到本机的特定端口的服务器套接字
public ServerSocket(int port) throws IOException
2.等待客户端请求连接:
监听并接收连接到本服务器的客户端连接,此方法会一直阻塞,直至有客户端请求连接
public Socket accept() throws IOException
3.获取客户端的输入输出流
4.关闭流
在Java中,客户端网络传输需要干的事情有:
1.建立与服务器之间的连接:
首先介绍下Socket:
Socket类代表客户端和服务器都用来互相沟通的套接字。客户端通过实例化来获取一个Socket对象,服务器通过accept()的返回值获取一个Socket对象。
//host是服务器的IP地址
//port是主机的端口号
public Socket(String host, int port) throws UnknownHostException, IOException
2.利用输入输出流进行数据传输。
3.关闭流
基于单线程的服务端与客户端通信
客户端:
package CODE.Socket聊天室;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//单线程客户端
//1.建立连接
//2.进行数据输入输出
public class SingleTreadClient {
public static void main(String[] args) {
try {
//1.建立连接
Socket client=new Socket("127.0.0.1",9999);
//2.进行数据输入输出
//客户端首先进行从服务器的数据读
Scanner clientInput=new Scanner(client.getInputStream());
if(clientInput.hasNext())
{
System.out.println("服务器say:"+clientInput.nextLine());
}
//客户端进行数据输出,但是先从键盘获取标准输入
System.out.println("请输入向服务器发送的消息");
Scanner in=new Scanner(System.in);
String str="";
if(in.hasNext())
{
str=in.nextLine();
}
//向服务器输出
PrintStream clientOut=new PrintStream(client.getOutputStream(),true,"UTF-8");
clientOut.println(str);
//3.关闭流
clientInput.close();
in.close();
clientOut.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端:
package CODE.Socket聊天室;
import Work.Exception.Ex;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
//单线程服务端
public class SingleThreadServer {
public static void main(String[] args) throws Exception {
//1.建立基站类
ServerSocket serverSocket=new ServerSocket(9999);
//2.等待客户端连接
System.out.println("等待客户端连接ing");
Socket client=serverSocket.accept(); //阻塞监听客户端连接
//3.连接成功后,获取客户端输入输出流
System.out.println("有新的客户端连接,客户端端口号为:"+client.getPort());
//客户端输出流
PrintStream clientOut=new PrintStream(client.getOutputStream(),true,"UTF-8");
clientOut.println("hello i am server");
//客户端输入流
Scanner clientInput=new Scanner(client.getInputStream());
if(clientInput.hasNext())
{
System.out.println("客户端端口号为"+client.getPort()+"say:"+clientInput.nextLine());
}
//4.关闭流
clientInput.close(); //关闭客户端输入流
clientOut.close(); //关闭客户端输出流
serverSocket.close(); //关闭基站
}
}
服务端:
客户端:
单线程:也就是说只有服务器只能连接一个客户端,并且读写数据顺序有限制,如果写在读之前,只有写了才能读,放在现实中就是在给好友发消息时,不能接受好友发过来的消息,这明显不合实际,所以,进入多线程,即读写线程并发。
多线程服务端和客户端通信
多线程客户端:
在多线程客户端中接受数据和写入数据可以并发。在实际通信中,对应在给好友发消息时可以接受到好友的消息。
package CODE.Socket聊天室;
//多线程客户端 发送和读取是并行
//读和写是2个线程,但是是一个socket,通过构造
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//关闭socket是写线程关闭,读线程只读
class ReadFromServer implements Runnable
{
//读线程
private Socket client;
public ReadFromServer(Socket client)
{
this.client=client;
}
@Override
public void run() {
//获取输入流来取得服务器发来的信息
try {
Scanner in=new Scanner(client.getInputStream());
while(true)
{
if(client.isClosed())
{
System.out.println("客户端已关闭");
in.close();
break;
}
else if(in.hasNext())
{
String msgFromServer=in.nextLine();
System.out.println("服务器发来的信息为:"+msgFromServer);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//写线程
class WriteToServer implements Runnable
{
private Socket client;
public WriteToServer(Socket client) {
this.client = client;
}
@Override
public void run() {
//从键盘获取标准输入
Scanner in=new Scanner(System.in);
PrintStream clientOut= null;
try {
clientOut = new PrintStream(client.getOutputStream(),true,"UTF-8");
while(true)
{
//获取向服务端发送的信息
String msgToServer="";
if(in.hasNext())
{
msgToServer=in.nextLine();
}
//向服务端输出信息
clientOut.println(msgToServer);
if(msgToServer.contains("bye"))
{
System.out.println("客户端"+client.getInetAddress()+"退出聊天室");
//客户端输出bye,关闭该客户端连接
clientOut.close(); //关闭输出流
in.close();
client.close(); //关闭socket,根据输入终止,
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MultiThreadClient {
public static void main(String[] args) {
try {
//1.建立连接
Socket client=new Socket("127.0.0.1",9999);
//2.启动读写线程,进行数据输入输出
Thread readThread=new Thread(new ReadFromServer(client));
Thread writeThread=new Thread(new WriteToServer(client));
readThread.start();
writeThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
多线程服务端:
在多线程中,服务端需要做到:
1.一个服务器可以接收多个客户端请求;
2.保存多个客户端连接
3.服务端中可以将客户端的请求保存,可以将客户端的数据发送指定客户端或所有客户端,可以在客户端退出时及时作出反应。在实际通信中,分别对应私聊、群聊、用户下线。
package CODE.Socket聊天室;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//多线程的服务器
//需要接收多个连接
public class MuliThreadServer {
//用ConcurrentHashMap是为了保证线程安全,存储每个连接的客户端
private static Map<String,Socket> clientMap=new ConcurrentHashMap<>();
//具体执行与每个客户端的通信 内部类,
private static class RealExcuteClient implements Runnable
{
private Socket client;
public RealExcuteClient(Socket client) {
this.client = client;
}
@Override
public void run() {
String msgFromClient="";
try {
Scanner in=new Scanner(client.getInputStream()); //获取客户端输入流
while(true)
{
if(in.hasNext())
{
msgFromClient=in.nextLine();
//由于在Windows在\r\n是换行,所以需要消除自带的\r,用" "替换掉
Pattern pattern=Pattern.compile("\r"); //表示识别"\r"的格式
Matcher matcher=pattern.matcher(msgFromClient); //msgFromClient是否有\r
//若有
msgFromClient=matcher.replaceAll(" ");
//注册:将连接的客户端添加到Map集合中
if(msgFromClient.startsWith("userName"))
{
String name=msgFromClient.split(":")[1];
if(addClientToMap(name,client))
continue;
else
break; //该客户端注册失败
}
//Group代表群聊,comment是内容,Group:comment
if(msgFromClient.startsWith("Group")){
String comment=msgFromClient.split(":")[1];
groupChat(comment);
continue;
}
//private代表私聊,name代表私聊对象,comment代表私聊的内容 private:name-comment
if(msgFromClient.startsWith("Private"))
{
String name=msgFromClient.split(":")[1].split("-")[0];
String comment=msgFromClient.split(":")[1].split("-")[1];
privateChat(name,comment);
continue;
}
//用户下线,从Map集合中删除该信息 用户名:内容
if(msgFromClient.contains("再见,下次再聊"))
{
String name=msgFromClient.split(":")[0];
quitChat(name);
break; //客户端自己会将连接关闭
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//注册
public static boolean addClientToMap(String name,Socket client)
{
//判断用户名是否已经存在
if(clientMap.get(name)!=null || client.isClosed())
{
System.out.println("用户名已经存在或连接已关闭,注册失败");
return false;
}
clientMap.put(name,client);
System.out.println("客户端"+name+"已经进入嗨聊空间");
System.out.println("客户端注册成功,"+"当前嗨聊空间人数为:"+clientMap.size());
try {
PrintStream out=new PrintStream(client.getOutputStream());
out.println(name+"注册成功");
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
//群聊
public static void groupChat(String comment)
{
//将Map集合变为Set集合
Set<Map.Entry<String,Socket>> clientSet=clientMap.entrySet();
for(Map.Entry<String,Socket> entry:clientSet)
{
//取得每一个客户端的socket
Socket sigClient=entry.getValue();
try {
PrintStream outToClient=new PrintStream(sigClient.getOutputStream());
outToClient.println(comment);
} catch (IOException e) {
System.out.println("群聊异常,错误为:"+e);
}
}
}
//私聊
public static void privateChat(String name,String comment)
{
Socket privateClient=clientMap.get(name); //获取对应的客户端socket
if(privateClient==null)
System.out.println("私聊失败,私聊对象不存在");
else{
try {
PrintStream privateOut=new PrintStream(privateClient.getOutputStream());
privateOut.println(comment);
} catch (IOException e) {
System.out.println("私聊异常,错误为:"+e);
}
}
}
//用户下线
public static void quitChat(String name)
{
System.out.println(name+"下线");
clientMap.remove(name);
System.out.println("当前嗨聊空间人数为:"+clientMap.size());
}
}
public static void main(String[] args) {
try {
//建立基站
ServerSocket serverSocket=new ServerSocket(9999);
//线程池,也就是最多允许15个客户端连接服务器
ExecutorService executorService= Executors.newFixedThreadPool(15);
for(int i=0;i<15;i++)
{
System.out.println("等待客户端连接");
//返回的是连接的客户端信息
Socket client=serverSocket.accept();
System.out.println("有新的客户端连接,客户端端口号为"+client.getPort());
executorService.submit(new RealExcuteClient(client));
}
executorService.shutdown();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
启动3个客户端,结果如下:
服务端: