文章目录
虚拟机要处于桥接模式下
在liunx上我们用ifconfig来查看IP号在Windows上用ipconfig上查看;
之前的代码一次只能处理一个客户端
套接字文件描述符也是文件描述符是0,1,2…不可能小于0;
清空saddr;
一,服务器端的代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);创建套接字,它能通过网络进行数据的收发,第一个为用啥协议,创建套接字的类型tcp协议,第三个协议版本基本为0
ipv4 使用tcp
assert(sockfd!=-1);sockfd文件描述符
struct sockaddr_in saddr,caddr;//saddr代表服务器端的地址,caddr代表客服端的地址
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;地址家族
saddr.sin_port=htons(6000);将6000这个端口转化为网络字节序列
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");将字符串(ip地址)转化为无符号整形
ifconfig命令找到的
int res=bind(sockfd,(struct sockaddr*)&caddr,sizeof(saddr));将ip,地址与套接字绑定
assert(res!=-1);
res=listen(sockfd,5);5为监听队列大小
assert(res!=-1);
while(1)
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&saddr,&len);c是连接套接字
if(c<0)
{
continue;
}
printf("accept c=%d,caddr.ip=%s,caddr.port=%d\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
把ip转化为字符串打印 网络转为主机端口
while(1)
{如果客户端建立连接之后没有发送数据选择结束,那么服务器端也要结束
char buff[128]={0};
int num=recv(c,buff,127,0);收到的字节数
if(num==0)说明客户端已经关闭了
{
break;
}
printf("buff=%s,num=%d\n",buff,num);
send(c,"ok",2,0);
}
close(c);
}
close(sockfd);
exit(0);
}
5是监听套接字的大小;
accept里的参数给出记录客户端IP和端口的那块空间;
把一个结构体转换成一个字符串;
网络转主机;
saddr记录套接字的地址;
在同一个网络里别人作为客户端发起链接(但是必须在同一网里)
看书liunx高性能服务
tcpdump抓包 查看三次握手
不会归0的方式
四次挥手
更改代码IP127.0.0.1
二,客户端的代码
#include<stdio,h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
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");
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
while(1)
{
printf("input\n");
char buff[128]={0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,127);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
}
close(sockfd);
exit(0);
}
上述的客户端和服务端代码存在如下弊端
第二个客户端的连接还没有建立此时仍然是第一个客户端的连接
recv返回值是0退出while循环
三,多线程TCP协议代码
每次接受一个链接就创建一个线程,用来serv接收数据,如果一个连接阻塞在了recv这里;主线程不会阻塞;会继续创建新的线程
服务端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
void*recv_thread(void*arg)
{
int c=(int)arg;
while(1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<=0)
{
break;
}
printf("recv(%d)=%s\n",c,buff);
send(c,"ok",2,0);
}
printf("client close\n");
}
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字,他能通过网络进行数据的收发; ipv4 使用tcp
assert(sockfd!=-1);//sockfd文件描述符;
struct sockaddr_in saddr,caddr;//saddr代表服务器端地址,caddr代表客户端地址;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;//地址家族
saddr.sin_port=htons(6000);//将6000这个端口转化为网络字节序列
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//将字符串(IP地址)转化为无符号整形 通过ifconfig命令查找IP号;
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//将ip地址与套接字绑定
assert(res!=-1);
res=listen(sockfd,5);//5为监听队列的大小
assert(res!=-1);
while(1)
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&saddr,&len);//c是连接套接字;
if(c<0)
{
continue;
}
printf("accept c=%d",c);
pthread_t id;
pthread_create(&id,NULL,recv_thread,(void*)c);
}
exit(0);
}
客户端代码
和上述客户端代码相同
运行结果:
四,三次握手
三次握手发生在connect()之后
五,四次挥手
六,tcp沾包
解决沾包
双方收到数据后发给对方数据,另一方就在等待,不会发数据就不会粘包
每次只接收一个字节的内容(将服务器端代码修改)
输出结果