文章目录
- 一、TCP网络编程
- (一)流程
- (二)相关函数
- 1. socket
- 2. bind
- 3. listen
- 4. accept
- 5. connect
- 二、收发函数
- (一)send函数
- (二)recv函数
- 三、TCP粘包问题
- (一)将两次消息间隔一定时间后再发送
- (二)关闭Nagle算法
- (三)定长发送数据
- 四、TCP下载程序
一、TCP网络编程
网络编程模型:
C/S模型:客户端服务器模型
优点:
客户端可以缓存一些数据,使用时直接在本地读取,无需每次重新下载;
由于客户端和服务器都是自己开发的,可以自定义协议
缺点:代码开发量大
B/S模型:浏览器服务器模型
优点:代码开发量较小
缺点:
客户端不能缓存数据,每次使用都需要重新从服务器端下载;
必须遵循http协议
(一)流程
服务器流程:
创建套接字–socket()
填充服务器的网络信息结构体
将套接字与服务器网络信息结构体绑定–bind()
将套接字设置成被动监听状态–listen()
阻塞等待客户端连接–accept()
收发数据–write()/read()
关闭套接字–close()
客户端流程:
创建套接字–socket()
填充服务器的网络信息结构体
(可以绑定,即指定端口号与服务器通信;可以不绑定,系统会分配一个临时端口号用于与服务器通信)
与服务器建立连接–connect()
收发数据–write()/read()
关闭套接字–close()
(二)相关函数
1. socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:
创建套接字
参数:
domain:通信域
AF_UNIX AF_LOCAL 本地通信使用
AF_INET IPV4使用
AF_INET6 IPV6使用
AF_PACKET 原始套接字使用
type:套接字类型
SOCK_STREAM TCP使用
SOCK_DGRAM UDP使用
protocol:附加协议 传0 表示没有附加协议
返回值:
成功 套接字(文件描述符)
失败 -1 重置错误码
2. bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
将套接字与服务器网络信息结构体绑定
参数:
sockfd:套接字
addr:网络信息结构体的首地址
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
//上面的结构体只是用来强转 防止编译警告的
//实际使用的是下面的结构体
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 网络字节序的端口号 */
struct in_addr sin_addr; /* 网络地址 */
};
struct in_addr {
uint32_t s_addr; /* 网络字节序无符号4字节整型的IP地址 */
};
addrlen:addr的长度
返回值:
成功 0
失败 -1 重置错误码
3. listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:
将套接字设置成被动监听状态
参数:
sockfd:套接字
backlog:指定半连接队列的最大长度
一般传 5 10 等都可以 不是 0 就行
//因为我们实际开发时 一般都是并发服务器 基本能维持半连接队列中没有元素
返回值:
成功 0
失败 -1 重置错误码
4. accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
提取半连接队列中的第一个链接,会返回一个新的套接字专门用于和该客户端通信
如果半连接队列中没有连接请求 accept函数会阻塞等待客户端连接
参数:
sockfd:套接字 必须是已经绑定了本地地址 且置成被动监听状态的
addr:用于保存客户端的网络信息结构体的缓冲区的首地址 不关心可以传NULL
addrlen:addr的大小,不关心也可以传NULL
返回值:
成功 新的套接字用于收发数据
失败 -1 重置错误码
5. connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
与服务器建立连接
参数:
sockfd:套接字
addr:要连接的服务器的网络信息结构体的首地址
addrlen:addr的长度
返回值:
成功 0
失败 -1 重置错误码
二、收发函数
(一)send函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:
向套接字上发送一条消息
参数:
sockfd:套接字
buf:要发送的数据的首地址
len:想要发送的字节数
flags:标志位 0 阻塞 MSG_DONTWAIT 非阻塞
返回值:
成功 实际发送的字节数
失败 -1 重置错误码
如果对方关闭了套接字或者断开连接,第一次send没有任何现象
但是第二次send会产生一个SIGPIPE
等价关系:
write(sockfd, buff, sizeof(buff));
send(sockfd, buff, sizeof(buff), 0);
sendto(sockfd, buff, sizeof(buff), 0, NULL, 0);
(二)recv函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:
在套接字上接收一条消息
参数:
sockfd:套接字
buf:用来存放接收到的数据的缓冲区的首地址
len:想要接收的字节数
flags:标志位 0 阻塞 MSG_DONTWAIT 非阻塞
返回值:
成功 实际接收的字节数
失败 -1 重置错误码
TCP的套接字,如果对方断开连接或者关闭套接字,recv会返回0
等价关系:
read(sockfd, buff, sizeof(buff));
recv(sockfd, buff, sizeof(buff), 0);
recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
三、TCP粘包问题
TCP协议中有一个Nagle 算法,他会通过减少数据包发送量来增进TCP/IP网上的性能。如果当前有一个部分填写的报文段(即还有可用空间可添加数据)在等待发送,那么就等到这个报文段填满或者达到最大延迟时间后再发送。
因为数据是没有边界的,因此可能会导致接收方在接收到数据后无法正常解析数据。
(一)将两次消息间隔一定时间后再发送
服务器程序中禁止使用sleep函数
(二)关闭Nagle算法
int value = 1;
setsockopt(sock_fd,IPPROTO_TCP,TCP_NODELAY,(char*)&value,sizeof(int));
(三)定长发送数据
还可以在包的开头标注有效数据长度。
四、TCP下载程序
server.c
client.c