Linux之socket编程(下)
目录
上一期我们已经学习了socket编程的相关接口,并且实现了一个简单的UDP网络小程序,本期的主要内容就是实现一个简单的TCP网络小程序。
简单的TCP网络小程序
tcp_sever.cc
#include<iostream>
#include<sys/types.h>
#include<cerrno>
#include<string>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
int main(int args ,char* argv[])
{
//1.创建套接字
int listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cout<<"listen_socket create erro"<<errno<<std::endl;
return 1;
}
//2.绑定IP+端口号
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_addr.s_addr=INADDR_ANY;
local.sin_port=htons(atoi(argv[1]));
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cout<<"bind error"<<std::endl;
return 2;
}
//3.TCP是面向连接的,所以得先监听
const int back_log=5;
if(listen(listen_sock,back_log)<0)
{
std::cout<<"listen error"<<std::endl;
return 3;
}
//4.监听到连接之后,在进行获取连接,获取连接如果没有获取成功,需要不断进行获取,所以需要循环
for(;;)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
if(new_sock<0)
{
continue;
}
//5.获取连接成功,提供服务
while(1)
{
char buffer[1024];
memset(buffer,0,sizeof(buffer));
size_t s=read(new_sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
std::cout<<"#client 发送: "<<buffer<<std::endl;
//读到客户端发送的数据之后,向客户端发送返回的数据
std::string msg="sever echo: ";
msg+=buffer;
write(new_sock,msg.c_str(),msg.size());
}
else if(s==0)
{
std::cout<<"client quit"<<std::endl;
break;
}
else{
std::cout<<"read error"<<std::endl;
break;
}
}
}
return 0;
}
其实tcp和udp的socket套接字代码很相似,只不过在tcp的sever端,多了监听连接和获取连接这两个步骤,同时获取连接的是持续不断地获取的,一次获取不到就继续获取。
tcp_client.cc
#include<iostream>
#include<cerrno>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
int main(int args,char* argv[])
{
//1.创建socket套接字文件
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
std::cout<<"socket create error"<<errno<<std::endl;
return 1;
}
struct sockaddr_in sever;
memset(&sever,0,sizeof(sever));
sever.sin_family=AF_INET;
sever.sin_addr.s_addr=inet_addr(argv[1]);
sever.sin_port=htons(atoi(argv[2]));
//2.client不需要绑定IP和端口号,所以直接建立连接即可。
if(connect(sock,(struct sockaddr*)&sever,sizeof(sever))<0)
{
std::cout<<"connect failed"<<std::endl;
return 2;
}
//3.连接成功了,使用服务
while(1)
{
std::cout<<"#请输入: ";
char buffer[1024];
memset(buffer,0,sizeof(buffer));
fgets(buffer,sizeof(buffer-1),stdin);
write(sock,buffer,sizeof(buffer)-1);
size_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
std::cout<<" sever echo"<<buffer<<std::endl;
}
}
return 0;
}
tcp的client端和udp的client端也类似,不过因为tcp是面向字节流的有连接通信,网络通信的类型变成了 SOCK_STREAM,同时tcp的client端新增了连接接口。
运行结果如下。
通过运行结果不难发现,当我们启动服务器进程时,一次只允许一个客户端进程去访问,这究竟是为什么呢?
这是因为在sever端只有一个执行流,且在while循环中死循环,无法跳出死循环去接收其它进程的连接,只要当前运行的一个客户端进程没有退出,sever端口就不能去持续的监听获取连接,所以就导致了一次只允许一个客户端进程去访问。
要允许多个client进程去访问,就得在sever进程里创建多个执行流。
只需要改造tcp_sever.cc的代码即可。
多进程版本如下。
#include <iostream>
#include <sys/types.h>
#include <cerrno>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
void service(int new_sock)
{
while (1)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
size_t s = read(new_sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
std::cout << "#client 发送: " << buffer << std::endl;
// 读到客户端发送的数据之后,向客户端发送返回的数据
std::string msg = "sever echo: ";
msg += buffer;
write(new_sock, msg.c_str(), msg.size());
}
else if (s == 0)
{
std::cout << "client quit" << std::endl;
break;
}
else
{
std::cout << "read error" << std::endl;
break;
}
}
}
int main(int args, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
// 1.创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
{
std::cout << "listen_socket create erro" << errno << std::endl;
return 1;
}
// 2.绑定IP+端口号
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(atoi(argv[1]));
if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
std::cout << "bind error" << std::endl;
return 2;
}
// 3.TCP是面向连接的,所以得先监听
const int back_log = 5;
if (listen(listen_sock, back_log) < 0)
{
std::cout << "listen error" << std::endl;
return 3;
}
// 4.监听到连接之后,在进行获取连接,获取连接如果没有获取成功,需要不断进行获取,所以需要循环
for (;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
if (new_sock < 0)
{
continue;
}
// 5.获取连接成功,提供服务
std::cout << "获取连接成功:" << new_sock << std::endl;
// 创建子进程,让子进程去创建服务
pid_t id = fork();
if (id == 0)
{
// 子进程,子进程继承父进程的文件描述符,但是是两份文件描述符,都指向了打开的同一文件
// 1.关闭父进程继承下来的监听套接字
close(listen_sock);
// 2.提供服务
service(new_sock);
// 3.服务提供结束之后,子进程关闭连接之后的新套接字
close(new_sock);
exit(0);
}
else if (id > 0)
{
// 父进程
// 要不要等待子进程?
// 如果是阻塞式等待,就必须等待子进程运行结束之后,父进程才能继续往下运行
// 这样的话还是相当于是串行的连接,所以我们不予等待,再开始忽略子进程的sigchild即可
// 父进程忽略子进程的sigchild信号之后,操作系统会自动回收子进程的相关资源,子进程同样
// 不会成为僵尸进程
//所以父进程啥都不用做
}
else
{
continue;
}
}
return 0;
}
与单进程版本的代码几乎类似,不过我们把创建服务的代码包装成了一个函数。且为了阻止父进程和子进程的串行化,我们让父进程忽略了子进程退出时的sigchild信号,这样就不用去等待子进程,可以在子进程执行服务代码的同时,让父进程继续接收其它进程的连接,这样就保证了多个client端进程可以同时访问sever端进程。
运行结果如下。
运行结果符合预期,实现了多线程场景下,多个client端进程访问sever端进程。
到此socket接口编程的学习到此结束。
本期内容到此结束^_^
原文地址:https://blog.csdn.net/qq_55958734/article/details/145301395
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!