什么是Socket?
Socket是连接运行在网络上的两个程序间的双向通讯的端点。
Socket进行网络通信的过程
1.服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户的连接请求。
2.客户程序根据服务器程序所在的主机名和端口号发出连接请求。
服务器在端口监听到有客户端发来的请求,如果有连接请求,那么接受连接请求并获得一个新的绑定到不同端口地址的套接字(不可能有两个程序同时占用一个端口),那么便可以调用线程去处理该请求,服务器继续在该接口监听其他客户端的请求。
客户和服务器通过读写套接字进行通讯。
其中:
服务器端的套接字ServerSocket类通过传入监听的端口号创建。
accept()方法监听向该端口发来的连接并接收连接。它将会阻塞直到连接被建立好。连接建立好后它会返回一个Socket对象。
连接建立好后,服务器端和客户端的输入流和输出流就互为彼此,即一端的输出流是另一端的输入流。
总结:使用ServerSocket和Socket实现服务器端和客户端的Socket通信
(1)建立Socket连接
(2)获得输入/输出流
(3)读/写数据
(4)关闭输入/输出流
(5)关闭Socket
服务端接受两个客户端的连接请求,并且通过线程池去处理客户端的连接请求。由于使用了线程池,那么系统可以接受很多客户端的请求而且保持较好的性能
Server端:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9999);
Socket socket = null;
//利用静态方法创建线程池,无需自己再设定复杂的参数
ExecutorService es = Executors.newCachedThreadPool();
while ((socket = ss.accept())!= null){
es.execute(new SocketTask(socket));
}
}
}
服务器端接受请求后是如何处理的?
import java.io.*;
import java.net.Socket;
public class SocketTask implements Runnable {
private Socket socket;
public SocketTask(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader br = null;
String input = "";
OutputStream os = null;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
input = br.readLine();
System.out.println("接收到消息:"+input);
os = socket.getOutputStream();
os.write("Hello Client!\r".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
os.flush();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Client 1:
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999);
OutputStream os = socket.getOutputStream();
// 末尾必须加终止符,否则另一端的bufferedreader.readline()方法会处于阻塞状态,直到流关闭!!!!
//非常关键,如果不加则无法通信
os.write("I am Dream\r".getBytes());
os.flush();
InputStream is = socket.getInputStream();
String input = new BufferedReader(new InputStreamReader(is)).readLine();
System.out.println("接受消息:"+input);
socket.close();
}
}
Client 2:
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999);
OutputStream os = socket.getOutputStream();
os.write("I am Thia\r".getBytes());
os.flush();
InputStream is = socket.getInputStream();
String input = new BufferedReader(new InputStreamReader(is)).readLine();
System.out.println("接受消息:"+input);
socket.close();
}
}
一个服务端和多个客户端实现双向通信,服务端维护一个基于端口号——套接字的哈希表,服务端每监听到有客户端发来的请求,便将连接建立好生成的套接字及通信的端口号放入哈希表。在服务端向客户端发送消息的时候,遍历哈希表,将消息发送至所有的客户端。(一对一的双向通信比较简单,不需要维护建立连接的客户端的套接字哈希表)
Server端实现:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class Server1 {
static Map<Integer,Socket> socketMap = new HashMap<>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9999);
while (true){
Socket socket = ss.accept();
socketMap.put(socket.getPort(),socket);
new RecvThread(socket).start();
new SendThread(socketMap).start();
}
}
}
class SendThread extends Thread{
private Map<Integer,Socket> socketMap;
public SendThread(Map<Integer,Socket> socketMap){
this.socketMap = socketMap;
}
@Override
public void run() {
try {
while (true) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(System.in));
String input = reader.readLine();
//!!!!重要
input += "\r";
for (Map.Entry<Integer,Socket> entry :socketMap.entrySet()) {
Socket socket = entry.getValue();
OutputStream os = socket.getOutputStream();
os.write(input.getBytes());
os.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class RecvThread extends Thread{
private Socket socket;
public RecvThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
while (true) {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String input = br.readLine();
System.out.println("接受消息来自端口" + socket.getPort() + " " + input);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client端:
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999);
Thread sendThread = new Thread(new Runnable() {
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
while (true) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(System.in));
String input = null;
input = reader.readLine();
//!!!!必须加
input+="\r";
os.write(input.getBytes());
os.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
sendThread.start();
Thread recvThread = new Thread(new Runnable() {
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
while (true) {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//readLine()是阻塞方法,直到读到内容并且遇到终止符(“\r”、“\n”、“\r\n”等等)或者到达流的末尾(返回Null)才返回
String input = br.readLine();
System.out.println(input);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
recvThread.start();
}
}
多个客户端都运行没有问题