一.在传输层实现TCP的代码-服务端与客户端
1.服务端流程:
①socket(int domain(地址族ipv4/ipv6),int type(服务类型,tcp->流式服务,udp->数据报服务),int protocol(协议版本,目前恒为0))-->创建套接字
②bind(int sockfd(套接字描述符),const struct sockaddr *addr(使用哪个IP的哪个端口),socklen_t addrlen(指针指向空间的大小))-->指定IP与端口
③listen(int sockfd(套接字描述符),int backlog(监听队列的大小))-->创建监听队列
④accept(int sockfd(监听套接字),struct sockaddr* addr(套接字的地址结构,存放客户端的ip与端口),socklen_t* addrlen(存放客户端套接字的大小))-->接受客户端的连接
⑤recv(int sockfd(套接字描述符),void * buf(传到哪个buff中),size_t len(buff多大),int flags(标志位))-->接收客户端发送过来的数据
⑥send(int sockfd(监听套接字),const void * buf(待发送的数据),size_t len(发送数据的大小),int flags(标志位))-->给客户端回复数据
⑦close(int fd(监听套接字))-->关闭连接
2.客户端流程:
①socket(int domain(地址符ipv4/ipv6),int type(服务类型,tcp->流式服务,udp->数据报服务),int protocol(协议版本,目前恒为0))-->创建套接字
②connect(int sockfd(套接字描述符),const struct sockaddr* addr(套接字地址结构的指针,服务器的Ip和端口),socklen_t addrlen(结构的大小))-->向服务器端发起连接
③send(int sockfd(套接字),const void * buf(待发送的数据),size_t len(发送数据的大小),int flags(标志位))-->向服务器发送数据
④recv(int sockfd(套接字描述符),void * buf(传到哪个buff中),size_t len(buff多大),int flags(标志位))-->接收服务器返回回来的数据
⑤close(int fd(监听套接字))-->关闭连接

3.实现客户端与服务端的连接,客户端从键盘输入给服务端可以发送消息,服务端收到打印出来并返回ok,客户端收到后将ok也打印到屏幕上。
①服务端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字(视作打开的文件,返回值为文件描述符)-->套接字可以通过网络收发数据,AF_INET为IPv4,SOCK_STREAM为TCP的流式服务
if(sockfd == -1)
{
printf("sockfd err\n");
exit(1);
}
struct sockaddr_in saddr,caddr;//sockaddr_in为IPv4专用,一般为sockaddr。saddr为指定端口ip时使用,caddr为监听套接字时使用
menset(&saddr,0,sizeof(saddr));//清空,本来saddr有四个结构,其中要求第四个结构要清为0,现在先将saddr全部清空为0,在重新定义其余三个结构即可
saddr.sin_family=AF_INET;//与创建套接字使用的地址符相同
saddr.sin_port=htons(6000);//指定端口号为6000,1024以内的值为知名端口号,1024-4096为保留端口,4096以上为临时端口。htons是将本主机的字节序列转换为网络字节序列,就是转换成大端形式,网络传输数据都是大端。
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//s_addr是无符号整形。inet_addr是将点分十进制的字符串形式的IP地址转换成无符号整形赋值给saddr。
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定,指定应用程序的IP端口。-->这里的强转是将IPV4专用的地址转成通用的地址 struct sockaddr_in 是IPV4专用
if(res==-1)
{
printf("res err\n");
exit(1);
}
res=listen(sockfd,5);//创建监听队列,sockfd为监听套接字
if(res==-1)
{
printf("listrn err\n");
exit(1);
}
//以上三步都不会阻塞
while(1)//接收连接
{
int len=sizeof(caddr);//accept接受连接,若没人连接则进行阻塞
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//c与sockfd交接套接字职责,c为链接套接字 -->当accept返回后,就会将返回客户端的ip地址和端口存放在caddr中,所以不用给caddr赋值,函数内部会给他赋值
if(c<0)
{
printf("accept err\n");
break;
}
printf("accept c=%d\n",c);//打印套接字的值
//等待对方发送数据
char buff[128]={0};
int n=recv(c,buff,127,0);//接收客户端发来的数据,若没发来数据就会阻塞(可以换成read)
printf("n=&s\n",buff);
//收到后给对方发消息
send(c,"ok",2,0);//可以换成write()
close(c);//关闭连接
}
}
②客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(sockfd==-1)
{
exit(1);
}
//连接服务器
struct sockaddr_in saddr;//指定服务器的IP与端口
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(saddr,(struct sockaddr*)&saddr,sizeof(saddr));//连接服务端
if(res==-1)
{
exit(1);
}
char buff[128]={0};
printf("input:\n");//打印出来说明连接成功了
fgets(buff,128,stdin);
send(sockfd,buff,strlen(buff),0);//给服务端发送数据
//接收服务端返回的数据
menset(buff,0,sizeof(buff));//先将Buff清空,在用buff接收
recv(sockfd,buff,127,0);
printf("buff=&s\n",buff);
close(sockfd);
exit(0);
}
4.在上述代码中,客户端每次只能发送一次数据,可以添加循环,使客户端一直发送数据,直到客户端输入end后结束客户端。(客户端与服务端均需要添加循环)
①服务端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()//循环的接收
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
printf("sockfd err\n");
exit(1);
}
struct sockaddr_in saddr,caddr;
menset(&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=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("res err\n");
exit(1);
}
res=listen(sockfd,5);
if(res==-1)
{
printf("listrn err\n");
exit(1);
}
while(1)
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
printf("accept err\n");
break;
}
while(1)
{
printf("accept c=%d\n",c);
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<=0)//客户端断开连接,n为收到字节的大小 -1为失败,0为客户端关闭了连接,关闭了会进行挥手通知服务端要关闭
{
break;
}
printf("n=&s\n",buff);
send(c,"ok",2,0);
}
printf("client close\n");
close(c);
}
}
②客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
exit(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(saddr,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
exit(1);
}
while(1)
{
char buff[128]={0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) == 0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
menset(buff,0,sizeof(buff));
recv(sockfd,buff,127,0);
printf("buff=&s\n",buff);
}
close(sockfd);
exit(0);
}
5.在改进的代码中,可以实现客户端与服务端循环的发送数据,但是当多个进程同时给一个服务端发送数据时,只能一个进程一个进程的发,无法做到多个进程一起发送数据。现在在服务端中进行fork操作(进程复制的方法),使得每一个客户端可以同时连接上服务端,可以做到同时发送数据。(只需要改动服务端,客户端与4中的相同)
①服务端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()//多个客户端可以同时连接服务端,并且同时发送数据
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
printf("sockfd err\n");
exit(1);
}
struct sockaddr_in saddr,caddr;
menset(&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=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("res err\n");
exit(1);
}
res=listen(sockfd,5);
if(res==-1)
{
printf("listrn err\n");
exit(1);
}
while(1)
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//连接操作只在父进程中,每当有一个客户端请求连接时,fork出一个子进程,每个进程间相互独立,这样就可以实现多个客户端与服务端的连接。
if(c<0)
{
printf("accept err\n");
break;
}
printf("accept c=%d\n",c);
pid_t pid=fork();//创建子进程
if(pid == 0)//在子进程中进行接收数据发送数据的操作
{
while(1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<0)
{
break;
}
printf("n=&s\n",buff);
send(c,"ok",2,0);
}
printf("client close\n");
close(c);//关闭子进程的连接
exit(0);//子进程退出
}
close(c);//关闭父进程中的套接字
}
}
6.服务端中listen函数:在Listen中会创建2个监听队列,一个是未完成3次握手,一个是已完成3次握手。如listen(sockfd,5)-->在linux系统下此时这个5就是可以存放5个已完成三次握手。在其他一些系统上可能是未完成+已完成的次数。在不同的内核下可以存放已完成三次握手的大小不同。(uname -a可以查看内核版本)(linux下listen已完成差不多是n+1)

7.服务端中accept函数:当监听队列中有已完成三次握手的数据时,accept函数就会运行,否则就会阻塞。
8.send()函数和recv函数:send每次发送数据都是先将数据放到发送缓冲区内,recv每次收到数据都是先放到接受缓冲区内。
服务端 客户端

recv打印缓冲区时,就是先将缓冲区现有的打印出来,可能有些数据还没有发过来,就是来了多少打印出多少。(netstat -natp(n是用数字显示ip地址 a是结果中会包含监听套接字 t是显示tcp连接 p是显示进程pid和名字))-->就是说发送的次数和接收的次数可能不是一一对应的。

983

被折叠的 条评论
为什么被折叠?



