目录
一、简介:
二、基础准备
1.IP地址转换函数:
2. 套接字地址结构
1.通用socket地址结构:
2.专用socket地址结构:
三、编程流程
四、服务器端编程流程
//头文件:
代码
//可以查看端口号
五、客户端编程:
六、连接后运行结果
七、循环写入数据
1.客户端代码:
2.服务器端代码:
3.执行结果:
八、如果用两个终端同时运行./client会怎样?
1.结果如何:
2.数据还能发送吗?
3.中间数据去哪了?
4.总结:
九、利用多线程,同时运行多客户端
1.代码:
2.运行结果:
一、简介:
在linux操作系统下完成TCP(socket、bind、listen、accept、recv、close、send、connect)网络编程,TCP服务器利用socket通过网络收发数据的能力,使用bind指定ip+端口、listen创建监听队列、accept接受链接、recv接受发送的数据、send回复数据以及最后利用close关闭链接;TCP客户端首先创建socket,利用connect向服务器发起链接,send发送数据,recv接受服务器反馈回来的数据,最后close关闭链接。能够在服务端程序和客户端程序互相传递信息,并能够使用fork和thread完成多进程和多线程编程,使多个客户端能够同时向服务器端发送数据。
二、基础准备
1.IP地址转换函数:
inet_addr :把字符串转换为整型
2. 套接字地址结构
通过socket可以在网络上进行数据的接收发送,(网络程序一定有套接字);
套接字包括IP地址和端口号
1.通用socket地址结构:
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
2.专用socket地址结构:
//IPv4
struct sockaddr_in
{
sa_family_t sin_family;//地址族
u_int16 sin_port;//端口号
struct om_addr sin_addr;//ip地址
};
//ipv6
struct in6_addr
{
unsigned char sa_addr[16];//IPV6地址,要用网络字节序表示
};
三、编程流程
四、服务器端编程流程
//头文件:
// man inet_addr
//服务器端两个套接字,一个是监听套接字,一个是连接套接字
//l链接n个客户端,就会产生n+1个描述符(多一个监听套接字)
//sockfd:监听套接字
//c:连接套接字
代码
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
//1.创建套接字
int sockfd=socket(AF_INET,SOCK_STREAM,0);//AF_INET是IPV4协议族
if (sockfd==-1)
{
printf("创建套接字失败\n");
exit(1);
}//创建套接字失败了
//2.设置IP端口
struct sockaddr_in saddr,caddr;//服务器端、客户端
memset(&saddr,0,sizeof(saddr));//把saddr全部置为0,即就是置空
saddr.sin_family=AF_INET;//成员设置地址族
saddr.sin_port=htons(6000);//主机转网络段整形,端口号,使用5000以上的端口,
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//回环地址,自己给自己发
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定套接字,专用强转成通用套接字地址 //给套接字指定,使用saddr中给的IP地址和端口
if(res==-1)//失败了,端口号(6000)被占用了;IP地址写错
{
printf("bind err\n");
close(sockfd);//关闭套接字
exit(1);//出错退出
}
//3.创建监听队列
res=listen(sockfd,5);//5是循环复用的,不代表只能接受五个客户端消息
if(res==-1)//失败了
{
close(sockfd);//如果忘记关闭,进程退出后也会自动关闭
exit(1);
}
//4.客户端申请连接
while(1)
{
int len=sizeof(caddr);//计算申请连接的客户端的套接字地址
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//可能会阻塞
if(c==-1)//或者c<0
{
continue;//失败就继续接收
}
printf("accept client ip:%s,port=%d\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
char buff[128]={0};
//5.服务器端接收连接
int n=recv(c,buff,127,0);//也可以用read,读取期望从c客户端的buff中读取127个字符
//recv也可能会阻塞,客户端不发数据
printf("recv:(%d):%s\n",n,buff);
//6.服务器端回复
send(c,"ok",2,0);//回复c客户端,2个字符的内容:“OK”,标志位为0
//7.关闭连接
close(c);//关闭
}
}
//可以查看端口号
五、客户端编程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//TCP固定搭配
if(sockfd==-1)//楚错了
{
exit(1);
}
//可以用bind(),绑定端口,但不这样,作为客户端,你只需要知道服务器的IP端口,发起连接,自己的IP端口系统分配即可;如果自己绑定,恰好用了已绑定的端口,反而用不成了,所以一般不指定
struct sockaddr_in saddr;//服务器的IP端口
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");
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)//失败:服务器没运行;没网络;端口写错
{
printf("connect failed\n");
close(sockfd);
exit(1);
}
printf("input\n");
char buff[128]={0};
fgets(buff,128,stdin);
send(sockfd,buff,strlen(buff)-1,0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
close(sockfd);
exit(0);
}
六、连接后运行结果
七、循环写入数据
1.客户端代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int socket_init();
int main()
{
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;
}
printf("accept c=%d\n",c);
while(1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<=0)//出错,或者对方关闭
{
break;
}
// printf("recv %s\n",buff);
printf("accept client ip:%s,port=%d rece:%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buff);
send(c,"OK",2,0);
}
close(c);
printf("close\n");
}
close(sockfd);
}
int socket_init()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
return -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=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.服务器端代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if (sockfd==-1)
{
printf("socket err\n");
exit(0);
}
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));
if(res==-1)
{
printf("connect failed\n");
exit(0);
}
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)-1,0);
memset(buff,0,128);
int n=recv(sockfd,buff,127,0);
if(n<=0)
{
printf("服务器关闭了");
break;
}
printf("buff=%s\n",buff);
}
close(sockfd);
}
3.执行结果:
八、如果用两个终端同时运行./client会怎样?
1.结果如何:
//第一个运行的终端发送成功,并且受到ok,但是第二个没有收到ok,服务器端也没有显示
2.数据还能发送吗?
//可以
//无法通过end退出第二个终端
//但在第一个终端发送end,两个终端上的都推出了;
//收到两个123,证明两个终端的内容都收到了
//在第一个终端发送end,第二个收到了ok
//即就是:在第一个终端的连接断开后,第二个连接上了,最后双方收到数据和OK
3.中间数据去哪了?
第二个终端发送的数据三次握手是已经建立好了的,但数据由于第一个终端达成了连接,第二个终端就在已完成三次握手的队列中,数据在服务器端的接收缓冲区;
//netstat -natp
// 50166端口的client有匹配的服务器端
//50172端口的client后面对应服务器是空的
//50172只有监听套接字,没有对应的服务端
//50172第二列的数字6,代表发了大小为6的内容,证明数据在服务器端的接收缓冲区
//圈起来的第一个./service第二列的1代表还有一个连接没处理(就是第二个终端运行的那个client)
4.总结:
如果可以循环发送,一个在发送,另一个会阻塞,直到第一个退出文章来源:https://www.toymoban.com/news/detail-761141.html
九、利用多线程,同时运行多客户端
1.代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
int socket_init();
void* fun(void* arg)
{
int c=(int)arg;
while(1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<=0)
{
break;
}
printf("buff(%d)=%s\n",c,buff);
send(c,"OK",2,0);
}
close(c);
printf("close\n");
}
int main()
{
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;
}
printf("accept c=%d\n",c);
pthread_t id;
pthread_create(&id,NULL,fun,(void*)c);
}
close(sockfd);
}
int socket_init()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
return -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=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.运行结果:
文章来源地址https://www.toymoban.com/news/detail-761141.html
到了这里,关于Linux--tcp编程(循环读数据、多客户端)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!