Linux&C++网络编程(1)—自己给自己发消息

avatar
作者
筋斗云
阅读量:0

最近入手C++网络编程知识,不求精通,但求能用。

项目准备

阿里云服务器(三个月免费版本)、SecuCRT

先导认识

项目是用自己的笔记本电脑充当客户端,也充当服务器。可能大家想问,服务器也可以自己充当?

其实客户端和服务器端只是个逻辑概念,并不是说一定要有两台物理设备两台电脑什么的。

项目步骤

1.首先克隆一个窗口,这是Linux系统的优点

左边来充当服务器端,右边来充当客户端。

服务器端代码为:

/*  * 程序名:demo2.cpp,此程序用于演示socket通信的服务端 */ #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <unistd.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> using namespace std;   int main(int argc,char *argv[]) {   if (argc!=2)   {     cout << "Using:./demo2 通讯端口\nExample:./demo2 5005\n\n";   // 端口大于1024,不与其它的重复。     cout << "注意:运行服务端程序的Linux系统的防火墙必须要开通5005端口。\n";     cout << "      如果是云服务器,还要开通云平台的访问策略。\n\n";     return -1;   }    // 第1步:创建服务端的socket。    int listenfd = socket(AF_INET,SOCK_STREAM,0);   if (listenfd==-1)    {      perror("socket"); return -1;    }      // 第2步:把服务端用于通信的IP和端口绑定到socket上。    struct sockaddr_in servaddr;          // 用于存放服务端IP和端口的数据结构。   memset(&servaddr,0,sizeof(servaddr));   servaddr.sin_family = AF_INET;        // 指定协议。   servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 服务端任意网卡的IP都可以用于通讯。   servaddr.sin_port = htons(atoi(argv[1]));     // 指定通信端口,普通用户只能用1024以上的端口。   // 绑定服务端的IP和端口。   if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )   {      perror("bind"); close(listenfd); return -1;    }     // 第3步:把socket设置为可连接(监听)的状态。   if (listen(listenfd,5) != 0 )    {      perror("listen"); close(listenfd); return -1;    }     // 第4步:受理客户端的连接请求,如果没有客户端连上来,accept()函数将阻塞等待。   int clientfd=accept(listenfd,0,0);   if (clientfd==-1)   {     perror("accept"); close(listenfd); return -1;    }    cout << "客户端已连接。\n";     // 第5步:与客户端通信,接收客户端发过来的报文后,回复ok。   char buffer[1024];   while (true)   {     int iret;     memset(buffer,0,sizeof(buffer));     // 接收客户端的请求报文,如果客户端没有发送请求报文,recv()函数将阻塞等待。     // 如果客户端已断开连接,recv()函数将返回0。     if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0)      {        cout << "iret=" << iret << endl;  break;        }     cout << "接收:" << buffer << endl;       strcpy(buffer,"ok");  // 生成回应报文内容。     // 向客户端发送回应报文。     if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0)      {        perror("send"); break;      }     cout << "发送:" << buffer << endl;   }     // 第6步:关闭socket,释放资源。   close(listenfd);   // 关闭服务端用于监听的socket。   close(clientfd);   // 关闭客户端连上来的socket。 }

客户端代码:

/*  * 程序名:demo1.cpp,此程序用于演示socket的客户端 */ #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <unistd.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> using namespace std;   int main(int argc,char *argv[]) {   if (argc!=3)   {     cout << "Using:./demo1 服务端的IP 服务端的端口\nExample:./demo1 192.168.101.139 5005\n\n";      return -1;   }    // 第1步:创建客户端的socket。     int sockfd = socket(AF_INET,SOCK_STREAM,0);   if (sockfd==-1)   {     perror("socket"); return -1;   }     // 第2步:向服务器发起连接请求。    struct hostent* h;    // 用于存放服务端IP的结构体。   if ( (h = gethostbyname(argv[1])) == 0 )  // 把字符串格式的IP转换成结构体。   {      cout << "gethostbyname failed.\n" << endl; close(sockfd); return -1;   }   struct sockaddr_in servaddr;              // 用于存放服务端IP和端口的结构体。   memset(&servaddr,0,sizeof(servaddr));   servaddr.sin_family = AF_INET;   memcpy(&servaddr.sin_addr,h->h_addr,h->h_length); // 指定服务端的IP地址。   servaddr.sin_port = htons(atoi(argv[2]));         // 指定服务端的通信端口。      if (connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))!=0)  // 向服务端发起连接清求。   {      perror("connect"); close(sockfd); return -1;    }      // 第3步:与服务端通讯,客户发送一个请求报文后等待服务端的回复,收到回复后,再发下一个请求报文。   char buffer[1024];   for (int ii=0;ii<6;ii++)  // 循环3次,将与服务端进行三次通讯。   {     int iret;     memset(buffer,0,sizeof(buffer));     //sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);  // 生成请求报文内容。    sprintf(buffer,"这是第%d个消息,编号%03d。",ii+1,ii+1);       // 向服务端发送请求报文。     if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0)     {        perror("send"); break;      }     cout << "发送:" << buffer << endl;      memset(buffer,0,sizeof(buffer));     // 接收服务端的回应报文,如果服务端没有发送回应报文,recv()函数将阻塞等待。     if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)     {        cout << "iret=" << iret << endl; break;     }     cout << "接收:" << buffer << endl;      sleep(1);   }     // 第4步:关闭socket,释放资源。   close(sockfd); } 

先运行服务器代码,Server是我自己命名的可执行文件名字。端口号因为是本机,所以都是5005.

如果是网址的话就是80了

然后在第二个窗口输入命令

ip addr

获取本机的IP地址,云服务器是私网IP ,不是公网IP了。注意,因为是自己连自己,所以是本机IP地址,如果你想连接真正的服务器,那么这里就要填写服务器的IP。

项目就成功了! 

代码详细认识

客户端代码

主函数有两个参数,第一个不管,我们在输入ip和端口时,因为都是属于字符串,所以会默认赋值给字符串。

这个是一个防傻瓜设计,就是如果你没有输入IP和端口,或者输入有误就会弹出提示公式和正确输入例子。

这是创建套接字,套接字的具体定义这里不细讲,套接字其实是一个函数,所以才会将其赋值,第一个参数是表示IP选择的宏,表示IPv4,这里99.99%都是IPv4,第二个是面向连接,第三个是选择TCP还是UDP,也可以设为0。

然后定义一个结构体指针,类型是hostent,然后将IP地址转换为结构体,gethostbyname函数的返回值是结构体类型。

然后创建sockaddr_in结构体对象,将其清空初始化,然后将其设为IPv4格式,然后memcpy与上一步对应,用以下代码把IP结构体地址(一般也叫大端序,一种数据排列方式。)复制到sockaddr_in结构体的sin_addr成员中。

memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);

最后一行,将main中输入的端口号转换为整数,就是atoi,然后再转换为大端序,就是htons,

最后,就是连接服务器,填写套接字,将sockaddr_in类型转换成sockaddr类型,这两个类型大小一致,为什么转换是因为_in不支持IPv6,sockaddr更有普适性。最后是大小。

服务器端代码

创建服务器的套接字

还是创建sockaddr_in的结构体,还是清空赋值,还是赋值为IPv4,然后是选择通过IP,但是这里是服务器方,这里表示可以接收哪些IP,如果是ANY就是说明可以接收任意IP,最后一个也是IP转换大端序,先转换成整数,然后再转换成大端序。

这里是绑定服务端的IP和端口,bind区分于客户端的connect,bind 第一个参数是服务端的套接字,第二个是强制转换结构体,第三个是大小。

然后调用listen函数,第一个参数是服务端的套接字,第二个默认是5

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!