最近入手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