Socket套接字——应用程序跨网络的通信
一、什么是socket?
TCP连接的两个端点称为socket,它右端口拼接到ip地址后构成(socket=ip地址:端口号)。每一条tcp连接唯一地被通信两端的两个socket确定。socket是应用进程和运输层协议之间的接口,它定义了应用进程为了获得网络通信服务时与操作系统交互的机制。
在套接字以上的进程受应用程序的控制,套接字以下的运输层协议软件受操作系统的控制。所以,应用程序需要使用tcp/ip协议进行通信是,就必须通过套接字与操作系统交互(使用系统调用函数)并请求其服务。
二、应用程序使用TCP服务过程中的系统调用顺序
1、建立连接阶段
不管是服务端还是客户端,当应用程序需要使用socket进行通信时,首先发起socket系统调用,请求操作系统创建一个“套接字”。这个调用的实际效果是请求操作系统把网络通信所需要的一些系统资源(存储器空间、CPU时间、网络带宽等)分配给应用进程。这些资源的总和用一个套接字描述符表示(套接字描述符中有一个指针指向存放套接字的地址)。操作系统将这个套接字描述符返回给应用程序。它是所有网络系统调用中的第一个参数。操作系统在处理的过程中,通过该描述符得到应用程序服务需要使用的资源信息。一台主机中可能存在多个套接字,它们的描述符用一个套接字描述符表来存放。
套接字被创建后,它的端口和ip都是空的,需要发起bind系统调用来绑定本地地址和端口。服务端是绑定本地地址和熟知的端口号(比如8080),客户端可以不调用bind,由操作系统分配动态端口号,通信结束后回收。
服务器调用bind后,还需要调用listen把套接字设置为被动状态,以便随时接受请求。无连接的UDP服务不调用listen。
然后,服务端继续调用accept,提取出客户端发来的请求。一个服务器可以同时处理多个连接,称为并发工作的服务器,器实现方式如下图:
如图所示:主服务器进程(M)每调用一次accept,就为一个新的请求建立一个连接套接字,并把这个套接字的标识符返回给客户端。同时,服务端创建一个从属服务进程(如:S1)来处理这个连接,后续数据的传送与接收都由这个从属服务进程负责。主服务进程的套接字重新调用accept,接收下一个请求连接。
客户端在调用socket创建套接字后,客户进程调用系统的connect发起连接请求(主动打开),connect调用时须指定远处服务器的ip和端口。
2、数据传输阶段
在建立TCP连接的基础上,客户端和服务器都通过sent系统调用传送数据,recv系统调用接收数据。
调用sent需要的三个参数:数据要发往的套接字描述符,要发送的数据地址和数据的长度。通常sent调用是把要发送的数据复制到操作系统内核的缓存中,如缓存已满,则sent暂时阻塞,直到缓存有空间为止。
调用sent也需要的三个参数:要使用的套接字描述符,缓存的地址和缓存的大小。
3、连接释放阶段
客户端和服务端不再使用套接字时,就将套接字撤销。调用close释放连接和撤销套接字,从属服务进程也随之撤销。
无连接的UDP服务不调用listen和accept。
三、Socket的其他含义
1、允许应用程序访问联网协议的应用程序接口API(Application Programming Interface),即应用层和运输层之间的接口,称为socket api,简称socket。
2、在调用socket API过程中使用的一个函数名也叫socket。
3、调用socket函数的端点,也称socket。
4、调用socket函数时的返回值(socket描述符),也叫socket。
5、在操作系统内核中连网协议的Berkeley实现,也称socket实现。
四、Java Socket编程示例
主服务进程:
package socket.chatroom;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @Classname MyServer
* @Description TODO
* @Date 2019/3/22 17:44
* @Created by luojia
*/
public class MyServer {
// 定义保存所有socket的list,属于类成员。
public static List<Socket> socketList= Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=new ServerSocket(30000);
while (true){
//此行代码会阻塞,一直等待别人连接
Socket s= serverSocket.accept();
socketList.add(s);
// 每当客户端连接后启动一个服务端线程为客户端服务
new Thread(new ServerThread(s)).start();
}
}
}
从服务线程:
package socket.chatroom;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
/**
* @Classname ServerThread
* @Description TODO
* @Date 2019/3/22 17:49
* @Created by luojia
*/
public class ServerThread implements Runnable{
// 定义当前线程所处理的socket
Socket socket=null;
//定义该线程处理的输入流
BufferedReader inputbr = null;
public ServerThread(Socket s) throws IOException {
this.socket=s;
//初始化输入流
inputbr=new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
public void run(){
String content=null;
//采用循环不断地从Socket中读取客户端发来的数据
while((content=readFromClient()) != null){
//遍历socketList中的每个socket,把收到的数据发送给每个客户端
for (Socket s:MyServer.socketList){
try {
//包装输出流
PrintStream printStream = new PrintStream(socket.getOutputStream());
printStream.println(content);
System.out.println(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//读取客户端数据
private String readFromClient(){
try {
return inputbr.readLine();
} catch (IOException e) {
//捕获到异常时,表明对应socket的客户端已经关闭
e.printStackTrace();
MyServer.socketList.remove(socket);
}
return null;
}
}
主客户端进程:
package socket.chatroom;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
/**
* @Classname MyClient
* @Description TODO
* @Date 2019/3/22 18:09
* @Created by luojia
*/
public class MyClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",30000);
//启动线程读取服务端发来的数据
new Thread(new ClientThread(socket)).start();
//获取socket输出流
PrintStream printStream = new PrintStream(socket.getOutputStream());
String line = null;
//不断读取键盘输入
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
while ((line=input.readLine()) != null){
//将键盘输入内容写入输出流
printStream.println(line);
}
}
}
从客户端线程:
package socket.chatroom;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @Classname ClientThread
* @Description TODO
* @Date 2019/3/22 18:12
* @Created by luojia
*/
public class ClientThread implements Runnable{
private Socket socket;
BufferedReader inputbr = null;
public ClientThread(Socket socket) throws IOException {
this.socket=socket;
this.inputbr=new BufferedReader(new InputStreamReader(socket.getInputStream()));
};
public void run(){
try {
String content = null;
//不断读取输入流中的内容,获取服务端发来的消息并打印出来
while ((content=inputbr.readLine()) != null){
System.out.println(content);
}
}catch (Exception e){
e.printStackTrace();
}
}
}