前言:为了让所学的计网知识融合于实际,让操作系统里的理论去满足工程需求,故通过借鉴30dayMakeServer的路线以及进行相应知识点的学习。
part1
首先我们要理解socket
为应用层和传输层提供应用编程接口(API)。
同层功能协议是水平交流,实际是
信息传输的时候是需要一个接口去完成对应信息的转换和传输,这里我们用的那个接口就是在Linux系统下使用的socket。
引入的头文件是
#include <sys/socket.h>
其中
socket(AF_INET, SOCK_STREAM, 0)
这三个参数含义分别为:
- 第一个参数:IP地址类型,AF_INET表示使用IPv4,如果使用IPv6请使用AF_INET6。
- 第二个参数:数据传输方式,SOCK_STREAM表示流格式、面向连接,多用于TCP。SOCK_DGRAM表示数据报格式、无连接,多用于UDP。
- 第三个参数:协议,0表示根据前面的两个参数自动推导协议类型。设置为IPPROTO_TCP和IPPTOTO_UDP,分别表示TCP和UDP。
每一个服务器都应该有一个唯一的标识符,而客户端要与其建立联系及传输信息,就要和这个标识符进行连接,
这个标识符由IP地址和网络端口组成,
客户端首先建立了socket这个应用编程接口,接下来要确定这个接口具体的连接对象的位置,这里我们使用
#include <arpa/inet.h> //这个头文件包含了<netinet/in.h>
这个头文件的,
这个结构体有三个子对象,
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(8888);
分别是设置地址族、IP地址和端口。
因为一个服务器的端口不应该只能提供给一个客户端,所以要将这个端口变成都可访问。
这里使用了一个函数,对socket这个接口进行泛化。
bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));
有接口,并且都能让其他客户端去连接,接下来在服务器端就要加一个监听函数,来的确认运气情况。
listen
函数监听这个socket端口,这个函数的第二个参数是listen函数的最大监听队列长度,系统建议的最大值SOMAXCONN
被定义为128。
listen(sockfd, SOMAXCONN);
要接受一个客户端连接,需要使用accept
函数。对于每一个客户端,我们在接受连接时也需要保存客户端的socket地址信息,于是有以下代码:
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_len = sizeof(clnt_addr);
bzero(&clnt_addr, sizeof(clnt_addr));
int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
要注意和accept
和bind
的第三个参数有一点区别,对于bind
只需要传入serv_addr的大小即可,而accept
需要写入客户端socket长度,所以需要定义一个类型为socklen_t
的变量,并传入这个变量的地址。另外,accept
函数会阻塞当前程序,直到有一个客户端socket被接受后程序才会往下运行。
客户端的写法和服务器很相似,但实现功能不同,是有点不一样的地方,在建立好接口以及绑定之后,是不需要泛化接口,之后就通过一个函数使它和服务器端连接。
connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));
这幅图就是服务器和客户端信息交换的流程图。
附上的代码是来自30dayMakeServer。
其GitHub地址:
https://github.com/yuesong-feng/30dayMakeCppServer
server部分:
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);//ip4/6 ,udp/tcp,0/??
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));//功能:置字节字符串s的前n个字节为零且包括‘\0’。
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(8888);
bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));//第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。
listen(sockfd, SOMAXCONN);//第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_len = sizeof(clnt_addr);
bzero(&clnt_addr, sizeof(clnt_addr));
int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);//要接受一个客户端连接,需要使用`accept`函数。对于每一个客户端,我们在接受连接时也需要保存客户端的socket地址信息
printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr)