网络中进程的唯一标识
网络层的IP地址可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(IP地址,协议,端口)就可以标识网络的进程了。
什么是socket
socket即套接字,用于描述IP地址和端口 ,是进行网络通信的API接口,通过套接字可以向网络发出请求或应答网络请求。
TCP的socket交互流程
- 服务器根据地址类型(ipv4、ipv6)、socket类型、协议创建socket。
- 服务器为socket绑定IP地址和端口号。
- 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时服务器的socket并没有被打开。
- 客户端创建socket。
- 客户端打开socket,根据服务器IP地址和端口号试图连接服务器socket。
- 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息才返回,开始接收下一个客户端连接请求。
- 客户端连接成功,向服务器发送连接状态信息。
- 服务器accept方法返回,连接成功。
- 客户端向socket写入信息。
- 服务端读取信息。
- 客户端关闭。
- 服务器端关闭。
服务器socket和客户端socket建立连接的部分其实就是三次握手过程:
socket接口函数
1. socket函数:
socket函数原型:int socket(int domain, int type, int protocol);
socket()创建成功就返回一个socket描述符,它唯一标识一个socket。但是这个返回的描述符存在于协议族空间中,并没有一个具体地址。如果想要给它赋予一个地址,就必须调用bind()函数,否则系统就在调用connect()、listen()时自动随机分配一个端口。
2. bind()函数:
bind()函数把一个地址族中的特定地址赋给socket。
原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd参数:即socket描述字,它是通过socket()函数创建来唯一标识一个sockett的,bind()函数就是将这个描述字绑定一个名字。
- addr参数:要绑定给sockfd的协议地址。
- addrlen参数:对应的是地址的长度。
- 返回值:成功返回0,失败返回SOCKET_ERROR。
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来连接服务器;而客户端就不用指定,由系统自动分配一个端口号和自身IP地址组合。这就是为什么通常服务端在调用listen之前会调用bind(),而客户端就不用调用,而是在connect()时由系统随机生成一个。
3. listen和connect函数:
服务器在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务端就会收到这个请求。
listen和connect的函数原型:
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen函数第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。
connect函数第一个参数为客户端的socket描述字,第二个参数为服务器的socket地址,第三个参数为socket地址长度。
客户端通过调用connect函数来建立与TCP服务器的连接。
4. accept函数:
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址;TCP客户端依次调用socket()、connect()之后就会向TCP服务器发送连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数接收请求,这样连接就建立好了,之后就可以进行网络I/O操作了。
accept函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
如果accept成功,返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
- 注意:
accept函数的第一参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常只创建一个监听socket描述字,它在改服务器的声明周期内一直存在;内核为每个进程创建一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
5. read和write函数:
至此服务器与客户端已经建立好连接了,可以调用网络I/O进行读写操作了。
read()函数原型:ssize_t read(int fd, void *buf, size_t count);
read函数负责从fd中读取内容。当读取成功时,read()返回实际所读的字节数,返回0表示已经读到文件的结束了,小于0表示出错。
write()函数原型:ssize_t write(int fd, const void *buf, size_t count);
write函数将buf中的内容写入fd。
6. close函数:
close函数需要包含头文件:#include<unistd.h>
函数原型:int close(int fd);
close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0时,才会触发TCP客户端向服务器发送终止连接请求。