一、TCP
(1)粘包问题
(2)包头组成(源端口号,目的端口,序列号,确认号,校验和,标志位,滑动窗口大小,紧急指针等)
序号:发送数据的编号
确认号:接收到数据的编号(只有当ACK为1时,该位有效)、确认号即想要让对方下次发送数据的序号
数据偏移:拆包组包过程中标识该包的偏移量
(3)TCP和UDP
1.UDP实现方式简单
资源开销比较小
UDP不安全、不可靠
UDP是无连接的,面向数据包的传输方式
2.TCP实现方式复杂
资源开销比较大
TCP安全、可靠
TCP是面向连接的,面向字节流传输方式
二、http协议
(1)客户端如何拿到服务器中的网页文件?
1.客户端向主机发送TCP链接请求
2.服务器收到请求后,与客户端链接成功
3.客户端向发送HTTP请求报文,告诉服务器想要的数据
4.服务器回复HTTP响应报文,将客户端要的数据发回
5.双方关闭通信
三、IO和阻塞
(1)多路复用
select:监听文件描述符集合,将所有要监听的事件加入集合中,使用select监听所有事件,当集合中有事件发生, select不再阻塞,同时select会将产生事件的文件描述符留在集合中,而把没有产生事件的文件描述符从集合中踢出,所以留在集合中的文件描述即为产生事件的文件描述符,对其处理即可。
(2)多路复用代码
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
fd_set rdfds;
fd_set tmpfds; //读文件描述符
int nready = 0;
int maxfd = 0; //最大文件描述符
int i = 0;
//创建监听套接字
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
//将sockfd加入监听集合中
FD_ZERO(&rdfds); //文件描述符集合清零
FD_SET(sockfd, &rdfds);//将sockfd加入文件描述符集合中
maxfd = sockfd;
while (1)
{
//开始监听
tmpfds = rdfds;
nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
if (-1 == nready)
{
perror("fail to select");
return -1;
}
//如果sockfd产生事件,处理新的连接请求,并将新的文件描述符加入集合,下次一起监听
if (FD_ISSET(sockfd, &tmpfds))
{
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
FD_CLR(sockfd, &rdfds);
close(sockfd);
continue;
}
maxfd = maxfd > confd ? maxfd : confd;
FD_SET(confd, &rdfds);
}
//遍历所有已经连接的客户端中是否有事件发生
for (i = sockfd+1; i <= maxfd; i++)
{
if (FD_ISSET(i, &tmpfds)) // 判断i是否仍在文件描述集合中
{
ret = HandleConnection(i);
if (-1 == ret)
{
printf("连接异常\n");
FD_CLR(i, &rdfds); //将i从集合中清除
close(i);
continue;
}
else if (0 == ret)
{
printf("连接断开\n");
FD_CLR(i, &rdfds);
close(i);
continue;
}
}
}
}
close(sockfd);
return 0;
}
int CreateTcpConnection(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int sockfd)
{
char tmpbuff[4096] = {0};
static int cnt = 0;
ssize_t nsize = 0;
sprintf(tmpbuff, "hello world --- %d", cnt);
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
cnt++;
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
return nsize;
}
int main(void)
{
int sockfd = 0;
int ret = 0;
sockfd = CreateTcpConnection(SER_IP, SER_PORT); //改自己的IP和端口
if (-1 == sockfd)
{
printf("连接服务器异常\n");
return -1;
}
while (1)
{
ret = HandleConnection(sockfd);
if (-1 == ret)
{
printf("连接出错!\n");
break;
}
else if (0 == ret)
{
printf("连接关闭\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}