首先实现一个简单的服务器端和客户端通信的小例子.
TCP方式
服务器端调用步骤:
- 调用socket函数创建套接字
- 调用bind函数分配IP地址和端口号
- 调用listen函数将套接字转为可接收连接状态
- 调用accept函数受理连接请求。如果在没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止。
- 使用write函数用于向客户端传输数据。
客户端:
- 调用客户端创建套接字
TCP相关API
创建socket
函数原型如下:
1 | int socket(int domain, int type, int protocol) |
socket函数成功时返回一个文件描述符(见后续其他相关知识),失败则返回-1.
domain: 套接字中使用的协议族信息
- PF_INET表示IPV4互联网协议族
type: 套接字数据传输类型信息
SOCK_STREAM表示面向连接的套接字
SOCK_DGRAM表示面向消息的套接字.
创建TCP套接字和UDP套接字:
1
2tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
protocol: 计算机间通信中使用的协议信息
描述socket
- 接下来代码描述socket文件,包括socket文件的大小,接受的内容等
1
2
3
4memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1]));sin_family: 地址族
每种协议族适用的地址族不同.IPV4使用4字节地址族.IPV6使用16字节地址族.
- AF_INET IPV4网络协议中使用的地址族
- AF_INET6 IPV6网络协议中使用的地址族
sin_port: 该成员保存16位端口号,它以网络字节序保存
sin_addr: 该成员保存32位IP地址信息,且也以网络字节序保存.
地址绑定
给套接字分配地址信息(IP地址和端口号)
1 | int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen); |
成功时返回0,失败时返回-1.
sockfd: 要分配地址(IP地址和端口号)的套接字文件描述符
myaddr: 存有地址信息的结构体变量地址值
addrlen: 第二个结构体变量的长度
监听
绑定了地址信息后,只需要最后一步接收来自客户的连接请求了.这就是让套接字变成监听状态,之后,只要客户端向服务器发送请求,服务器就可以接收到了.
通过listen函数进入等待连接请求状态.只有调用了listen函数,客户端才能进入可发出连接请求的状态.换言之,这时客户端才能调用connect函数(若提前调用将发生错误).1
int listen(int sock, int backlog);
成功时返回0,失败时返回-1.
sock:希望进入等待连接请求状态的套接字文件描述符,传递的描述符套接字参数成为服务器端套接字(监听套接字)
backlog: 连接请求等待队列的长度, 若为5,则队列长度为5,表示最多使5个连接请求进入队列.等待连接请求状态:
- 服务端处于等待连接请求状态是指,客户端请求连接时,受理连接前一直使请求处于等待状态.
- 连接请求队列:允许的连接请求的客户端个数
接受连接
函数原型如下:
1 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
sock: 服务器套接字的文件描述符
addr: 保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充客户端地址信息.
addrlen: 第二个参数addr结构体的长度,但是存有长度的变量地址.函数调用完成后,该变量即被填入客户端地址长度.
返回值:accept函数成功时返回文件描述符,失败时返回-1
accept函数受理连接请求队列中待处理的客户端连接请求.函数调用成功时,accept函数内部将产生用于数据IO的套接字,并返回其文件描述符.套接字是自动创建的,并自动与发起连接请求的客户端建立连接
一个服务器端socket可以接收很多客户端的连接请求.当客户端发起连接请求时,服务器端需要调用accept函数受理客户端的请求.同时来自不同客户端的连接也会当成不同的socket文件,返回不同的文件描述符.
connect
1 | int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen); |
- 功能:客户端调用connect函数后,发生以下情况之一才会返回(完成函数调用)
- 服务器接收连接请求
- 注意:接收连接请求并不意味着服务器调用accept函数,而是指服务器把连接请求信息记录到等待队列
- 所以,connect函数返回后并不立即进行数据交换
- 发生断网等异常情况而中断连接请求
- 服务器接收连接请求
- sock: 客户端套接字文件描述符
- servaddr: 保存目标服务端地址信息的变量地址值
- addrlen: 以字节为单位传递已传递给第二个结构体参数servaddr的地址变量长度
read和write
1 | ssize_t read(int fd, void* buf, size_t nbytes); |
成功时返回接收的字节数(但遇到文件结尾则返回0),失败时返回-1.
fd: 显示数据接收对象的文件描述符.
buf: 要保存接收数据的缓冲地址值.
nbytes: 要接收数据的最大字节数.
1 | ssize_t write(int fd, const void* buf, size_t nbytes); |
成功时返回写入的字节数,失败时返回-1.
fd: 显示数据传输对象的文件描述符.
buf: 要保存传输数据的缓冲地址值.
nbytes: 要传输数据的最大字节数.
fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
- 功能:从给定流 stream 读取数据到 ptr 所指向的数组中
- ptr – 这是指向带有最小尺寸 size\nmemb* 字节的内存块的指针。
- size – 这是要读取的每个元素的大小,以字节为单位。
- nmemb – 这是元素的个数,每个元素的大小为 size 字节。
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
fgets
函数原型为:char *fgets(char *str, int n, FILE *stream)
功能:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。fgets会把读到字符写到s指向的字符串里,直到出现下面某种情况:
- 遇到换行符
- 已经传输了n-1个字节
- 到达文件尾
它会把遇到的换行符也保存到s中.最后在s末尾加上一个表示结尾的空字符\0.一次调用最多只能传输n-1个字符,因为它必须把空字符加到字符串表示结尾.
gets函数类似于fgets,但是它从标准输入流中取数据并丢弃遇到的换行符.它在接收字符串的尾部加上一个空字节(一个字符占一个字节).
fgetc
fgetc从文件指针stream指向的文件中读取一个字符,读取一个字节后,光标位置后移一个字节。
fputc
功能:把一个字符写到一个输出文件流中.它返回写入的值,如果失败,则返回EOF.
putc函数的作用相当于fputc,但它可能被实现为一个宏.
putchar相当于putc(c, stdout),但它把单个字符写到标准输出.注意:putchar和getchar都是把字符当做int类型而不是char类型来使用的.这就允许文件尾标识取值-1,这是一个超出字符数字编码范围的值.
fopen
https://blog.csdn.net/hairetz/article/details/4150193
sigaction
1 | sigaction(int signo, const sigaction* act, struct sigaction *oldact); |
- 功能:
- signo:与signal函数相同,传递信号信息
- act:对应于第一个参数的信号处理函数(信号处理器)信息
- oldact:通过此参数获取之前注册的信号处理函数指针,若不需要则传递0
- 返回值:成功时返回0,失败时返回-1
UDP方式
服务端:
客户端:
UDP相关API
TCP套接字将会保持与对方套接字的连接。换言之,TCP套接字知道目标地址信息。但UDP套接字不会保持连接状态,因此每次传输数据都要添加目标地址信息。
sendto
1 |
|
功能:
返回值:成功时返回传输的字节数,失败时返回-1。
sock:用于传输数据的UDP套接字文件描述符。
buff:保存待传输数据的缓冲地址值。
nbytes:待传输的数据长度,以字节为单位。
flags:可选项参数,若没有则传递0。
to:存有目标地址信息的sockaddr结构体变量的地址值。
addlen:传递给参数to的地址值结构体变量长度。
这个函数在发送端主机上被调用,而发送数据的UDP套接字只有在发送端主机才有意义。
recvfrom
1 |
|
返回值:成功时返回接收的字节数,失败时返回-1。
sock:用于接收数据的UDP套接字文件描述符。
buff:保存接收数据的缓冲地址值。
nbytes:可接收的最大字节数,故无法超过参数buff所指的缓冲区大小。
flags:可选项参数,若没有则传入0。
from:存有发送端地址信息的sockaddr结构体变量的地址值。
addlen:保存参数from的结构体变量长度的变量地址值。
其他相关知识
- 文件描述符
- 对linux来说,一切皆文件.套接字和本地的文件都是文件.
- 文件描述符是系统分配给文件或套接字的整数.是为了方便称呼操作系统分配的文件或套接字.
- 结构体sockaddr_in
参考文献
[1] TCP/IP网络编程:https://blog.csdn.net/u011675745/article/details/78555250