Scoket介绍
利用 TCP 协议进行通信的两个应用程序是有主次之分的,
一个是服务器程序,一个是客户端程序,
两者的功能和编写方法不太一样,
其中 ServerSocket 类表示 Socket 服务器端,Socket 类表示 Socket 客户端。
- 服务器端流程:
(1)创建套接字(ServerSocket)
(2)将套接字绑定到一个本地地址和端口上(bind)
(3)将套接字设定为监听模式(),准备接受客户端请求(listen)
(4)阻塞等待客户端请求到来。当请求到来后,接受连接请求,返回一个新的对应于此客户端连接的套接字sockClient(accept)
(5)用返回的套接字sockClient和客户端进行通信(send/recv);
(6)返回,等待另一个客户端请求(accept)
(7)关闭套接字(close) - 客户端流程:
(1) 创建套接字(socket)
(2) 向服务器发出连接请求(connect)
(3) 和服务器进行通信(send/recv)
(4) 关闭套接字(close)

ServerSocket 类
用于在服务器上开一个端口,被动地等待数据(使用 accept() 方法)并建立连接进行数据交互。
服务器套接字一次可以与一个套接字连接,如果多台客户端同时提出连接请求,
请求连接的客户端会被存入一个队列中,然后从中取出一个套接字与服务器新建的套接字连接起来。
若请求连接大于最大容纳数,则多出的连接请求被拒绝;默认的队列大小是 50。
ServerSocket 的构造方法
ServerSocket 的构造方法如下所示。
ServerSocket():无参构造方法。
ServerSocket(int port):创建绑定到特定端口的服务器套接字。
ServerSocket(int port,int backlog):使用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口。
ServerSocket(int port,int backlog,InetAddress bindAddr):使用指定的端口、监听 backlog 和要绑定到本地的 IP 地址创建服务器。
创建 ServerSocket 时可能会拋出 IOException 异常,所以要进行异常捕捉。
try{
ServerSocket serverSocket=new ServerSocket(8111);
}
catch(IOException e){
e.printStackTrace();
}
ServerSocket 的常用方法
ServerSocket 的常用方法如下所示。
1.Server accept():监听并接收到此套接字的连接。
2.void bind(SocketAddress endpoint):将 ServerSocket 绑定到指定地址(IP 地址和端口号)。
3.void close():关闭此套接字。
4.InetAddress getInetAddress():返回此服务器套接字的本地地址。
5.int getLocalPort():返回此套接字监听的端口。
6.SocketAddress getLocalSocketAddress():返回此套接字绑定的端口的地址,如果尚未绑定则返回 null。
7.int getReceiveBufferSize():获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是从 ServerSocket 接收的套接字的建议缓冲区大小。
调用 accept() 方法会返回一个和客户端 Socket 对象相连接的 Socket 对象,
服务器端的 Socket使用 getOutputStream() 方法获得的输出流
将指向客户端 Socket 使用 getInputStream() 方法获得那个输入流。
同样,服务器端的 Socket使用的 getInputStream() 方法获得的输入流将指向
客户端 Socket 使用的 getOutputStream() 方法获得的那个输出流。
也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之同样如此。
Socket 类
Socket 类表示通信双方中的客户端,用于呼叫远端机器上的一个端口,
主动向服务器端发送数据(当连接建立后也能接收数据)。
Socket 的构造方法
Socket 的构造方法如下所示。
Socket():无参构造方法。
Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口。
Soclcet(InetAddress address,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。
Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口。
Socket(String host,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。
Socket 的常用方法
Socket 的常用方法如下所示。
void bind(SocketAddress bindpoint):将套接字绑定到本地地址。
void close():关闭此套接字。
void connect(SocketAddress endpoint):将此套接字连接到服务器。
InetAddress getInetAddress():返回套接字的连接地址。
InetAddress getLocalAddress():获取套接字绑定的本地地址。
InputStream getInputStream():返回此套接字的输入流。
OutputStream getOutputStream():返回此套接字的输出流。
SocketAddress getLocalSocketAddress():返回此套接字绑定的端点地址,如果尚未绑定则返回 null。
SocketAddress getRemoteSocketAddress():返回此套接字的连接的端点地址,如果尚未连接则返回 null。
int getLoacalPort():返回此套接字绑定的本地端口。
int getPort():返回此套接字连接的远程端口。
例子-客户端与服务器端的简单通信
Server
public class Server {
public static void main(String[] args)throws IOException {
ServerSocket serverSocket =new ServerSocket(); //无参数,表示没有连接客户端
serverSocket.bind(new InetSocketAddress("127.0.0.1",40000));
//用一个死循环,不断的接收客户端请求连接
while(true) {
//接收客户端的请求,监听客户端
Socket socket=serverSocket.accept();
//通过客户端的sokcet对应的输入流来接收数据,因为我是在dos中进行测试,所以编码格式为GBK,保证中文不乱码
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(),"GBK"));
String str = in.readLine();
System.out.println("客户端来的信息: "+str);
//发送信息
PrintStream ps = new PrintStream(socket.getOutputStream(),true,"GBK");
ps.println("我是服务器!"); //打印输出流,会发送
ps.close();
socket.close();
//serverSocket.close();
}
}
}
Client
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",40000));
//发送数据
PrintStream ps = new PrintStream(socket.getOutputStream(),true,"GBK");
ps.println("我是客户端!");
//接收数据
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(),"GBK"));
String str=in.readLine();
System.out.println("服务器端发送来的信息:"+str);
in.close();
ps.close();
socket.close();
}
}
基于BIO实现网络通信
前面的课中,其实已经粗略地实现了一个基于BIO的一个网络通信实例,
但是,我们这章中的目标是实现一个基于命令行的能群聊的聊天室案例,
针对这个目标,前面的例子也就只能是一个简单的客户端与服务器端的通信例子,
很多细节的东西,需要我们进一步的完善!
进一步思考,我们要做一个聊天室,既要实现在公共频道说的话,所有的客户端都看得见,知道是哪个说的,也要实现私聊的功能。应该怎么样进一步的完善,前面的简单例子!?
首先:暂时把服务器端发送给客户端的死信息去掉,需要在服务器端使用List来保存所有链接进来的客户端的Socket,
要用多线程来处理每个链接上来的客户端。服务器端要把收到的所有客户端发送来的数据“广播”给其他的客户端。
客户端也要升级,发送给服务器的信息,不能是死的,也要用多线程,主线程负责读取用户在键盘上输入的信息,子线程负责读取和显示服务器端发送过来的数据。
再次:实现私聊功能,也就是说一个客户端可以将信息发送给另一个指定的客户端。
实际上,所有客户端只与服务器端链接,客户端直接并没有互联,
也就是说,当一个客户端发送信息后,
服务器端必须要判断信息到底是向所有用户发送,还是向指定用户发送。
这些功能就要求,每个客户端必有有自己的特征属性!
这里更具体的细节:
- 客户端发送来的信息必须要有标记,让服务器能够判断信息是公聊信息,还是私聊信息,是哪个用户发的。
- 如果是私聊信息,客户端信息的目标客户端是哪个,服务器必须知道,才能转发到指定的客户端。
解决办法:在客户端发送的信息上动手脚,在信息前加前后缀,这些特殊前后缀字符就是协议字符。
这些协议字符,有的标记公聊还是私聊,有的标记客户端身份…
服务器端第二次升级:定义一个接口规定协议字符串,定义一个数据结构保存聊天室用户和对应Socket关联的输出流直接的映射关系,更新Server类添加异常捕获,更新ServerThread类添加判断信息类型。
客户端第二次升级:更新Client类添加用户名的输入,也就是登录功能,更新获取键盘信息并发送的功能,要加上协议字符前后缀,更新ClientThread类关闭流。
例1:简单的升级,服务端发送接收到的每一个客户端信息,再广播到每个客户端
服务器端,每接到一个客户请求,就分一个线程
public class Server {
public static List<Socket> socketList= Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) throws IOException {
ServerSocket serverSocket =new ServerSocket(); //无参数,表示没有连接客户端
serverSocket.bind(new InetSocketAddress("127.0.0.1",40000));
//用一个死循环,不断的接收客户端请求连接
while(true) {
//接收客户端的链接请求,获取链接进来的客户端的socket,加入到队列中
Socket clientSocket = serverSocket.accept();
Server.socketList.add(clientSocket);
//多客户端的情况下,我们要所有的客户端都保存起来,不能象前面一样,单线程出来链接进来的客户端,效率太低,我们要用多线程来处理,每来一个客户端请求,就分配一第线程
new Thread(new ServerThread(clientSocket)).start();
}
}
ServerThread.java-线程类,负责接收信息广播
public class ServerThread implements Runnable {
private Socket socket= null;
private BufferedReader br=null;
public ServerThread(Socket socket) throws IOException {
this.socket = socket;
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"GBK"));
}
@Override
public void run() {
int i=0;
String content = null; //收到一个客户的信息,是公开的,全广播出去
//用循环不断的接收,不断的广播
while((content=readFromClient())!=null) {
//接收客户消息,不是私有的全广播到所有客户端
for(Socket s:Server.socketList) {
try {
PrintStream ps =new PrintStream(s.getOutputStream(),true,"GBK");
ps.println(content); //广播,
System.out.println("多线程启动了!"+(i++));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//单独地定义方法:服务器接收客户端信息
private String readFromClient() {
try {
return br.readLine();
//服务器读远端客户信息,有可能远端突然关闭了,服务器就读不到,所以设这个方法,方便循环调用,
}catch(Exception e) {
Server.socketList.remove(socket);
}
return null;
}
}
客户端
/*
* 群聊聊天室客户端
*/
public class BioClientChat {
public static void main(String[] args)throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",40000));
//开发一个线程类,创建一个子线程来负责获取和显示从服务器那里发送来的数据信息
new Thread(new ClientThread(socket)).start();
//发送数据,我们这里不用死信息,而是从键盘上输入活的信息,再发送
PrintStream ps =new PrintStream(socket.getOutputStream(),true,"GBK");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in,"GBK"));
String str = null;
while((str=br.readLine())!=null) {
ps.println(str);
}
br.close();
ps.close();
socket.close();
}
}
-----------------------------------------------------------线程类ClientThread.java负责接收信息,打印到控制台中-------------------------------
public class ClientThread implements Runnable{
private Socket socket=null;
private BufferedReader in =null;
public ClientThread(Socket socket) throws IOException {
this.socket= socket;
in=new BufferedReader(new InputStreamReader(socket.getInputStream(),"GBK"));
}
@Override
public void run() {
try {
//正式的实现获取和显示从服务器哪里发送来的信息
String content = null;
//不断的接收
while((content=in.readLine())!=null) {
System.out.println(content);
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
测试:
- 配置环境变量:

- 复制客户端两个文件到任一位置,例:c:\xiong下,云掉
打包的一行。

- 在文件夹地址栏中,输入cmd,回车,打开dos窗口,自动定位到当前文件夹位置,此时我打开n个,用来模拟n个客户。在
其中的一个dos下javac BioClientChat.java,编译,再在其它客户端处java BioClientChat,执行程序。


1508

被折叠的 条评论
为什么被折叠?



