目录
前言
本人最近在学习TCP通信的时候发现TCP只能实现在局域网下任意两个客户端的通信,但是对于非局域网内的客户端,并不能直接访问,查询了众多资料后,终于找到了解决办法,能够实现任意两个或者多个非局域网客户端的相互通信,在此介绍给大家!
一、TCP为什么不能直接在两个私网客户端之间通信?
这就设计到网络通信的原理了,在网络通信中,任意两个局域网的设备之间是可以直接访问的,但是如果两台设备不在同一个局域网下,是不能直接访问的。但是如果其中一台设备拥有公网域名或者公网ip,他们两个之间就可以直接访问。根据这个原理,我们可以借用一台拥有公网IP的设备作为中转站服务器,然后服务器接收客户端A的信息,再将客户端A的信息转发给客户端B,实现了非局域网TCP通信。
二、使用步骤
1.申请阿里云esc云服务器
在阿里云官网申请ESC云服务器,新用户可以免费试用七天。然后配置阿里云服务器,首先设置安全组,如下图所示:
配置完云服务器后可以直接通过远程登陆登陆到阿里云服务器上,第一次登陆可能会让你设置密码账号,界面如下:
然后我们将服务端代码移植到服务器上就可以了
如果你能够在局域网下实现TCP通信以及多线程的相关操作,那么一下代码是能够看懂的。
有几个关键点总结一下:
1.服务端的代码中的IP地址要填阿里云服务器的公网IP地址。
2.这里的原理是通过多线程实现的,当检测到有客户端连接时,通过pthread_create()函数创建子进程与客户端通信,父进程依旧在检测等待第二个客户端连接。
3.服务端是如何区分两个客户端的?是通过accept()函数的返回值描述符来区分的,通过将客户端返回的描述符存放在数组c_fd[2]中,然后通过write()函数分别发送。
代码如下(示例):
#include "stdio.h"
#include "stdlib.h"
#include "assert.h"
#include "string.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "arpa/inet.h"
#include "netinet/in.h"
#include "unistd.h"
#include "pthread.h"
int ret;
int i=0;
int c_fd[2];
struct sockaddr_in saddr,caddr;
pthread_t t1;
pthread_t t2;
char BUFF1[256];
char BUFF2[256];
int read_1;
int read_2;
int param=100;
void *thread1(void *arg)
{
while(1)
{
memset(BUFF1,0,sizeof(BUFF1));
printf("Client1 address is %s\n",inet_ntoa(caddr.sin_addr));
read_1=read(c_fd[0],BUFF1,255);
if(read_1==-1) {printf("read client1 is error");}
else { printf("I read client1:%s\n",BUFF1);
write(c_fd[1],BUFF1,strlen(BUFF1));
}
}
}
void *thread2(void *arg)
{
while(1)
{
memset(BUFF2,0,sizeof(BUFF2));
printf("client2 address is %s\n",inet_ntoa(caddr.sin_addr));
read_2=read(c_fd[1],BUFF2,255);
if(read_2==-1) {printf("read client2 is error");}
else { printf("I read client2:%s\n",BUFF2);
write(c_fd[0],BUFF2,strlen(BUFF2));
}
}
}
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
// struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(8000);
saddr.sin_addr.s_addr=inet_addr("172.17.124.48");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
res=listen(sockfd,5);
assert(res!=0);
int len=sizeof(caddr);
while(1)
{
if(i==0)
{
c_fd[i]=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c_fd[i]<0){continue;}
i=1;
printf("I am client1\n");
}else {
c_fd[i]=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c_fd[i]<0){continue;}
i=2;
printf("I am client2\n");
}
if(c_fd[0]==-1)
{printf("client1 is connect error");}
else
{ ret=pthread_create(&t1,NULL,thread1,(void *)¶m);
assert(ret!=-1);
}
if(c_fd[1]==-1)
{printf("client2 is connect error");}
else
{ ret =pthread_create(&t2,NULL,thread2,(void *)¶m);assert(res!=-1);}
}
return 0;
}
2.在两个客户端运行客户端代码
这里的代码与同一局域网下的代码几乎没有什么区别,主要在于IP地址需要更改,这里的IP要填阿里云服务器的内网IP,记住!是内网IP,然后其他几乎没有什么变化,端口号也要一致。
代码如下(示例):
#include "stdio.h"
#include "stdlib.h"
#include "sys/socket.h"
#include "sys/types.h"
#include "unistd.h"
#include "assert.h"
#include "arpa/inet.h"
#include "string.h"
#include "netinet/in.h"
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
printf("socket=%d\n",sockfd);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(8000);
saddr.sin_addr.s_addr=inet_addr("39.103.227.125");
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
while(1)
{
printf("please input:\n");
char buff[255]={0};
fgets(buff,255,stdin);
if(strncmp(buff,"end",3)==0){break;}
send(sockfd,buff,strlen(buff)-1,0);
char date[255]={0};
int n=recv(sockfd,date,254,0);
printf("i recive to aliyun:%s\n",date);
}
close(sockfd);
}
第二个客户端的代码就不给出了,与这个几乎没有区别
三、运行服务端客户端代码
先运行云服务器上面的客户端代码,再运行两个客户端代码
左上角为虚拟机,左下角为另一局域网下的树莓派,右边为阿里云服务器,现在连接成功后服务端成功打印
通过实例可以看出代码正常运行,能够达到我们的要求效果
总结
这个公网TCP通信头疼了我好几天,因为一直不知道服务端如何区分客户端,因为在父进程和子进程中套接字和端口号都是一样的,后来在翻阅资料发现可以通过accept()函数的描述符区分客户端,然后成功编写了如下代码,效果跟预想一样。