网络编程: socket套接字

451 阅读3分钟
原文链接: zhuanlan.zhihu.com

我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标识网络中的一个进程。

socket是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。socket(英文名字叫做插座,插口)套接字,简单的直译就是说通过套接字我们可以实现网络连接,数据收发。

下面是常见的网络编程的交互图:

工作流程如下:

  • 服务端在客户端连接之前就必须已经初始化
1. 初始化socket
2. 执行bind函数,把自己的服务能力绑定在一个IP以及端口上
3. 执行listen操作,把原先的socket转换为服务端的socket
4. 服务端最后阻塞在 accept 上等待客户端请求的到来。
5. 服务端已经准备就绪
  • 客户端初始化一个socket
  • 执行connect像服务端的IP以及端口发起连接请求(IP以及端口客户端需要预先知道)
  • 三次握手成功后,客户端与服务端建立连接,开始数据通信
  • 客户端进程向操作系统内核发起 write 字节流写操作,内核协议栈将字节流通过网络设备传输到服务器端,服务器端从内核得到信息,将字节流从内核读入到进程中,并开始业务逻辑的处理,完成之后,服务器端再将得到的结果以同样的方式写给客户端
  • 客户端如果需要与服务端断开连接,需要调用close函数,操作系统内核此时会通过原先的连接链路向服务器端发送一个 FIN 包,服务器收到之后执行被动关闭,这时候整个链路处于半关闭状态,此后,服务器端也会执行 close 函数,整个链路才会真正关闭。半关闭的状态下,发起 close 请求的一方在没有收到对方 FIN 包之前都认为连接是正常的;而在全关闭的状态下,双方都感知连接已经关闭。

三次握手

接下来我们通过node.js实现客户端与服务器的socket通信。

// serve.js

const net = require('net');

const server = net.createServer(socket => {
  console.log(`连接已经建立 IP: ${socket.remoteAddress},端口: ${socket.remotePort}`);
  socket.on('data', data => {
    console.log(`接受client发送的数据, ${data}`);
  });
  socket.write('我是来自服务器端');
  socket.on('close', () => {
    console.log('客户端关闭连接');
  });
  socket.on('end', () => {
    console.log('客户端发起FIN包');
  });
}).listen(9999);

server.on('listen', () => {
  console.log(`serve listening ${server.address()}`);
});



// client.js

const net = require('net');

const port = 9999;
const host = '127.0.0.1';
const client = new net.Socket();

client.connect(port, host, socket => {
  client.write('我是来自客户端');
});

client.on('data', data => {
  console.log(`接受来自服务器的数据: ${data}`);
});

client.on('error', err => {
  console.log(err);
  client.destroy();
});

client.on('close', () => {
  console.log('服务端关闭连接');
});

先启动服务端, 然后执行客户端发起连接请求。

然后我们让客户端退出连接。

可见完成了一个简单的socket编程demo。