tcp
Warning
未完待续
1 socket
- 与网络中的其他应用进行通信的函数接口
- 封装了传输层的协议, tcp udp
- 既然是与网络中的其他应用程序通信, 那么我们可以推断socket套接字肯定有ip(确定主机)和port(确定应用),用来明确是哪个应用
2 代码
client.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
// tcp client
int main(int argc, const char* argv[])
{
// 创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("socket error");
exit(1);
}
// 连接服务器
struct sockaddr_in serv_addr;
// 将serv_addr 结构体 所占内存内容置为0, 等价于 bzero(&serv_addr, sizeof(serv_addr));
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
//网络传输数据采用的是大端 ,所以这里端口要转换为大端
// h: host, to:to, n: net, s:short int (unsigned)
// 将主机字节序转为网络字节序(大端)
serv_addr.sin_port = htons(9999);
// 将ip转换为网络字节序, 是将字符串(ip) 转为int型(大端方式)
// 直接修改 &serv_addr.sin_addr.s_addr
// inet_pton转换成功则返回1,转换失败返回 0,如果指定的地址类型协议簇不合法,将返回-1
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret == -1)
{
perror("connect error");
exit(1);
}
// 通信
while(1)
{
// 写数据
// 接收键盘输入
char buf[512];
printf("input: \n");
fgets(buf, sizeof(buf), stdin);
// 这里如果输入的小于512 会就最后的回车符号也发送过去.(详情见fgets的说明)
// 你可以自己将最后的回车符处理掉...
// 发送给服务器, +1 是把最后的\0 也发送过去.
write(fd, buf, strlen(buf)+1);
// 接收服务器端的数据
int len = read(fd, buf, sizeof(buf));
if(len == -1)
{
perror("read error");
break;
}
else if( len == 0 )
{
printf("服务端关闭了连接 ...\n");
break;
} else if(len > 0)
{
printf("read buf = %s, len = %d\n", buf, len);
}
}
close(fd);
return 0;
}server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
int main(int argc, const char* argv[])
{
// 创建用于监听的套节字
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd == -1)
{
perror("socket error");
exit(1);
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 地址族协议ipv4
serv_addr.sin_port = htons(9999); // 本地端口需要转换为大端
// ip转为网络字节序(大端)
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 0表示用本机的任意IP
int ret = bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
// 设置监听
ret = listen(listen_fd, 64);
if(ret == -1)
{
perror("listen error");
exit(1);
}
// 等待并接受连接请求
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if(client_fd == -1)
{
perror("accept error");
exit(1);
}
char client_ip_buf[64];
// 网络字节序 转换为 主机字节序, 结果存储到client_ip_buf
// 返回值是个指向client_ip_buf 的指针
const char* client_ip=inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip_buf, sizeof(client_ip_buf));
printf("client ip: %s, port: %d\n", client_ip, ntohs(client_addr.sin_port));
// 通信
while(1)
{
// 先接收数据
char buf[1024] = {0};
int len = read(client_fd, buf, sizeof(buf));
if(len == -1)
{
perror("read error");
break;
}
else if(len > 0)
{
// 顺利读出了数据
printf("read buf = %s\n", buf);
for(int i=0; i<len; ++i)
{
buf[i] = toupper(buf[i]);
}
printf(" toupper: %s\n", buf);
// 数据发送给客户端
write(client_fd, buf, strlen(buf)+1);
}
else if( len == 0 )
{
printf("client disconnect ...\n");
break;
}
}
close(listen_fd);
close(client_fd);
return 0;
}3 tcp 报文
4 握手-传输-挥手
5 滑动窗口
6 修改缓冲区大小
6.1 系统tcp缓冲设置
查看配置
cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 5369920
# 4096 是说接收缓冲区(读缓冲区)的最小值
# 87380 表示默认值 该值会覆盖rmem_default, 不是被覆盖, 网络上很多说被覆盖.. 真的服了
# 5369920 接收缓冲区空间的最大字节数(该值会被rmem_max覆盖)
cat /proc/sys/net/core/rmem_max #一个socket的读缓冲区可由程序设置的最大值
212992
cat /proc/sys/net/core/rmem_default #一个socket的被创建出来时,默认的读缓冲区大小
2129926.2 查看linux关于缓冲的源码
linux4.9源码 net/core/sock.h
linux4.9源码 net/core/sock.c
switch (optname) {
case SO_SNDBUF:
// 如果 val> /proc/sys/net/core/wmem_max ,则设置成wmem_max 的值
val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
// 如果 val值的 2倍 还小于最小值, 那么 使用 最小值,否而就是 val值的2倍
sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);
sk->sk_write_space(sk);
break;
case SO_RCVBUF:
val = min_t(u32, val, sysctl_rmem_max);
set_rcvbuf:
sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
sk->sk_rcvbuf = max_t(int, val * 2, SOCK_MIN_RCVBUF);
break;
}
得出结论
- 如果你设置的值 > 最大值,则 设置为 最大值的2倍
- 如果你设置的值的2倍 < 最小值,则 设置为 最小值
- 其他情况 ,则设置为 该值的2倍
6.3 代码修改缓冲
server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>
void getsockopts(char* s ,int sockfd){
printf("===%s===\n",s);
int opt_val;
socklen_t opt_len=sizeof(opt_val);
if (getsockopt(sockfd,SOL_SOCKET, SO_SNDBUF, &opt_val, &opt_len)!=0){
perror("getsocketopt error");
exit(1);
}
printf("写缓冲区大小: %d\n", opt_val);
opt_val=0;
if (getsockopt(sockfd,SOL_SOCKET, SO_RCVBUF, &opt_val, &opt_len)!=0){
perror("getsocketopt error");
exit(1);
}
printf("读缓冲区大小: %d\n", opt_val);
opt_val=0;
if (getsockopt(sockfd,SOL_SOCKET, SO_SNDLOWAT, &opt_val, &opt_len)!=0){
perror("getsocketopt error");
exit(1);
}
printf("发送低潮限度大小: %d\n", opt_val);
}
void setsockopts(int sockfd,int nRecvBuf,int nSendBuf){
// 接收缓冲区
int ret=setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
if (ret !=0){
perror("setsocketopt error");
}
//发送缓冲区
ret =setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
if (ret !=0){
perror("setsocketopt error");
}
}
int main(int argc, const char* argv[])
{
// printf("argcount: %d\n",argc);
if (argc!=3){
printf("请输入要设置的读缓冲和写缓冲\n");
printf("usage: %s 读缓冲值 写缓冲值\n",argv[0]);
exit(1);
}
// 创建用于监听的套节字
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd == -1)
{
perror("socket error");
exit(1);
}
getsockopts("before set",listen_fd);
// 这里不严谨,随便用用
setsockopts(listen_fd,atoi(argv[1]),atoi(argv[2]));
getsockopts("after set",listen_fd);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
// 设置监听
ret = listen(listen_fd, 64);
if(ret == -1)
{
perror("listen error");
exit(1);
}
// 等待并接受连接请求
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if(client_fd == -1)
{
perror("accept error");
exit(1);
}
char client_ip_buf[64];
const char* client_ip=inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip_buf, sizeof(client_ip_buf));
printf("client ip: %s, port: %d\n", client_ip, ntohs(client_addr.sin_port));
// 通信
while(1)
{
sleep(3);
}
close(listen_fd);
close(client_fd);
return 0;
}./server2 212993 1
argcount: 3
===before set===
写缓冲区大小: 16384
读缓冲区大小: 87380
发送低潮限度大小: 1
===after set===
写缓冲区大小: 4608 # 最小值
读缓冲区大小: 425984 # 最大值的2倍
发送低潮限度大小: 1
./server2 212991 1
===before set===
写缓冲区大小: 16384
读缓冲区大小: 87380
发送低潮限度大小: 1
===after set===
写缓冲区大小: 4608
读缓冲区大小: 425982 # 设置的值的2倍
发送低潮限度大小: 17 阻塞
阻塞 ,阻塞, 啥是阻塞?
1. 假设快递, 在没有蜂巢的情况下, 快递员带着包裹来了, 你人不在, GG
2. 有蜂巢后, 包裹到了, 快递员发现蜂巢满了, GG