一、tcp相关概念
tcp协议特点:面向连接的、可靠的、流式服务
建立链接:三次握手,发送 SYN
断开链接;四次挥手,发送 FIN
tcp、udp都同属于传输层,在网络层使用ip协议,都要将数据交给IP协议,但ip协议是无连接、不可靠的
tcp的可靠性:
应答确认、
超时重传
乱序重排(先发出去的报文不一定先到,路劲可能不一样)
去重(发送确认信息可能丢失导致报文多发)
滑动窗口,进行流量控制(收发信息时,程序处理能力有限,缓存区的大小有限,当发送过快超出接收能力范围时,会对齐进行控制,通过滑动窗口进行流量控制)
流式服务(连续多次的send,可能recv的情况不一样,可能多收也可能只收一部分,不是一一对应的)
粘包:两次发送的数据可能背一次收到,多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
多个客户端链接服务端代码
同时运行两个客户端进行链接
结束其中一个客户端,一个客户端进行链接
结束两个客户端,无客户端进行连接
将服务器端关闭
代码:
1、
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
int socket_init();
void recv_data(int c)
{
while( 1 )
{
char buff[128] = {0};
int n = recv(c,buff,127,0);//0代表
if( n <= 0)//<代表失败,==代表客户端关闭
{
break;
}
printf("buff = %s\n",buff);
send(c,"ok",2,0);
}
close(c);
}
void wait_child(int sig)
{
wait(NULL);
}
int main()
{
signal(SIGCHLD,wait_child);
//创建套接字
int sockfd = socket_init();
if( sockfd == -1)
{
exit(0);
}
while( 1 )
{
//定义表示套接字地址的变量
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr* )&caddr,&len);//会有阻塞
if( c < 0)
{
continue;
}
pid_t pid = fork();
if( pid == -1)
{
close(c);
continue;
}
if(pid == 0)//判断是否在子进程,为1为子进程
{
//接收客户端数据
recv_data(c);
exit(0);
}
close(c);
//wait(NULL);//能解决将死进程,但影响应用要求
}
}
int socket_init()
{
//创建一个在传输层使用tcp协议的一个套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0); //AF_INET --地址zhu,目前的固定的、服务类型 ---- tcp流式服务
if(sockfd == -1)//创建失败
{
return -1;
}
//定义一个套接字地址,一个ipv4 专用的地址
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);//网络字节 大端
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//将字符串转成无符号整形
//指定ip端口
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if( res == -1)
{
printf("bind err\n");
return -1;
}
//创建监听队列
res = listen(sockfd,5);
if( res == -1)
{
return -1;
}
return sockfd;
}
2、signal(SIGCHLD,SIG_IGN);
//明确的忽略信号,相当于告诉系统子进程结束了,直接将其内核中的tcp处理掉,不考虑退出码 在只能在linux上使用,不能在unix上使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
int socket_init();
void recv_data(int c)
{
while( 1 )
{
char buff[128] = {0};
int n = recv(c,buff,127,0);//0代表
if( n <= 0)//<代表失败,==代表客户端关闭
{
break;
}
printf("buff = %s\n",buff);
send(c,"ok",2,0);
}
close(c);
}
void wait_child(int sig)
{
wait(NULL);
}
int main()
{
//signal(SIGCHLD,wait_child);
signal(SIGCHLD,SIG_IGN); //明确的忽略信号,相当于告诉系统子进程结束了,直接将其内核中的tcp处理掉,不考虑退出码 在只能在linux上使用,不能在unix上使用
//创建套接字
int sockfd = socket_init();
if( sockfd == -1)
{
exit(0);
}
while( 1 )
{
//定义表示套接字地址的变量
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr* )&caddr,&len);//会有阻塞
if( c < 0)
{
continue;
}
pid_t pid = fork();
if( pid == -1)
{
close(c);
continue;
}
if(pid == 0)//判断是否在子进程,为1为子进程
{
//接收客户端数据
recv_data(c);
exit(0);
}
close(c);
//wait(NULL);//能解决将死进程,但影响应用要求
}
}
int socket_init()
{
//创建一个在传输层使用tcp协议的一个套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0); //AF_INET --地址zhu,目前的固定的、服务类型 ---- tcp流式服务
if(sockfd == -1)//创建失败
{
return -1;
}
//定义一个套接字地址,一个ipv4 专用的地址
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);//网络字节 大端
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//将字符串转成无符号整形
//指定ip端口
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if( res == -1)
{
printf("bind err\n");
return -1;
}
//创建监听队列
res = listen(sockfd,5);
if( res == -1)
{
return -1;
}
return sockfd;
}