原文作者:aircraft
原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html
c++ 网络编程(一)TCP/UDP 入门级客户端与服务端交互代码
网络编程和套接字
-
网络编程其实和我们计算机上的文件读取操作很类似,通俗地讲,网络编程就是编写程序使两台联网的计算机相互交换数据。那么,数据具体怎么传输呢?其实操作系统会提供名为“套接字”的部件,套接字就是网络数据传输用的软件设备而已。即使你对网络数据传输原理不太熟悉,你也可以通过套接字完成数据传输。因此,网络编程常常又称为套接字编程。
-
下面我们再通过一个通俗地例子来理解什么是套接字并给出创建它的过程。实际上,这个过程类似我们的电话机系统,电话机通过固定电话网完成语言数据的交换。这里的电话机就类似我们的套接字,电网就类似我们的互联网。和电话可以拨打或接听一样,套接字也可以发送或接收。先来看看接收的套接字创建过程:
1,打电话首先需要准备什么?当然得是要先有一台电话机。创建相当于电话机的套接字,如下:int socket(int domain, int type, int protocol);
2,准备好电话机后要考虑分配电话号码的问题,这样别人才能联系到你。套接字也一样,利用下面函数创建好套接字分配地址信息(IP地址和端口号)。
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
3,做了上面两步后,接下来就是需要连接电话线并等待来电。一连接电话线,电话机就转为了可接听状态,这时其他人可以拨打电话请求连接到该机了。同样,需要把套接字转化成可接收连接的状态。
int listen(int sockfd, int backlog);
4,前面都做好后,如果有人拨打电话就会响铃,拿起话筒才能接听电话。套接字同样如此,如果有人为了完成数据传输而请求连接,就需要调用下面函数进行受理。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-
总结下网络中接收连接请求的套接字创建过程如下:
第一步:调用socket函数创建套接字。 第二步:调用bind函数分配IP地址和端口号。 第三部:调用listen函数转为可接收请求状态。 第四步:调用accept函数受理连接请求。 -
上面讲的都是接电话,即服务端套接字(接收),下面我们再来讲讲打电话,即客服端套接字(发送)。这个要简单,只有两步:1,调用socket函数创建套接字。2,调用connect函数向服务端发送连接请求。
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
基于Linux的文件操作
1,在这里为什么要讨论Linux上的文件操作呢?因为Linux上,socket操作与文件操作没有区别,在Linux上,socket也被认为是文件的一种。
注:Linux上的C语言编译器–GCC,具体使用就不在这里讲了。
2,文件描述符:是系统自动分配给文件或套接字的整数。下面我们再来通过一个例子理解下它:假设学校有个打印室,只需要打个电话就能复印所需论文。有一位同学,经常打电话要复印这样个内容:“<<关于随着高度信息化社会而逐渐提升地位的触觉,知觉,思维,性格,智力等人类生活质量相关问题特性的人类学研究>>这篇论文第26页到30页”。终于有一天,打印室的人感觉这样太不方便了,于是,打印室的人和那位同学说:“以后那篇论文就编为第18号,你就说帮我复印18号论文26页到30页”。在该例中,打印室相当于操作系统,那位同学相当于程序员,论文号相当于文件描述符,论文相当于文件或套接字。也就是说,每当生成文件或套接字,操作系统就会自动返回给我们一个整数。这个整数就是文件描述符,即创建的文件或套接字的别名,方便称呼而已。
注:文件描述符在Windows中又称为句柄。
3,Linux上的文件或套接字操作:
打开文件:
int open(const char *path, int flag); –> (Linux上对应socket(…)函数)
关闭文件或套接字:
int close(int fd); –>(Windows上对应closesocket(SOCKET S)函数)
将数据写入文件或传递数据:
ssize_t write(int fd, const void *buf, size_t nbytes);
读取文件中数据或接收数据:
ssize_t read(int fd, void *buf, size_t nbytes);
注释:ssize_t = signed int, size_t = unsigned int,其实它们都是通过typedef声明的,为基本数据类型取的别名而已。既然已经有了基本数据类型,那么为什么还需要为它取别名呢?是因为目前普遍认为int是32位的,而过去16位操作系统时代,int是16位的。根据系统的不同,时代的变化,基本数据类型的表现形式也随着变化的。如果为基本数据类型取了别名,以后要修改,也就只需要修改typedef声明即可,这将大大减少代码变动。
基于Windows平台的实现
1,Windows套接字大部分是参考BSD系列UNIX套接字设计的,所以很多地方都跟Linux套接字类似。因此,只需要更改Linux环境下编好的一部分网络程序内容,就能再Windows平台下运行。
2,上面讲了Linux上,文件操作和套接字操作一致。但Windows上的I/O函数和套接字I/O函数是不同的。
Winsock数据传输函数:
int send(SOCKET s, const char *buf, int len, int flags);
Winsock数据接收函数:
int recv(SOCKET s, const char *buf, int len, int flags);
3,Windows与Linux上的套接字再一个区别是:Windows上需要先对Winsock库进行初始化,最后退出还要注销Winsock相关库。
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
第一个参数:Winsock中存在多个版本,应准备WORD类型的(WORD是typedef声明的unsigned short)套接字版本信息。若版本为1.2,则其中1是主版本号,2是副版本号,应传递0x0201。高8位为副版本号,低8位为主版本号。我们还可以直接使用宏,MAKEWORD(1,2); //主版本号为1,副版本为2,返回0x0201。 第二个参数:就是传入WSADATA型结构体变量地址。
Winsock库初始化:
int main(int argc, char *argv[]){ WSADATA wsaData; ... if(WSAStartup(MAKEWORD(1,2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); ... return 0;}
在退出时需要释放Winsock库:
int WSACleanup(void); //返回0成功,失败返回SOCKET_ERROR
代码:
windows下:
TCP:
一.服务端代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include#include #include #pragma comment(lib, "ws2_32.lib")void main(){ WSADATA wsaData; int port = 5099; char buf[] = "Server: hello, I am a server....."; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("Failed to load Winsock"); return; } //创建用于监听的套接字 SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(port); //1024以上的端口号 addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN)); if (retVal == SOCKET_ERROR){ printf("Failed bind:%d\n", WSAGetLastError()); return; } if (listen(sockSrv, 10) == SOCKET_ERROR){ printf("Listen failed:%d", WSAGetLastError()); return; } SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR);//等待客户请求到来 SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, &len); if (sockConn == SOCKET_ERROR){ printf("Accept failed:%d", WSAGetLastError()); //break; } printf("Accept client IP:[%s]\n", inet_ntoa(addrClient.sin_addr)); //发送数据 int iSend = send(sockConn, buf, sizeof(buf), 0); if (iSend == SOCKET_ERROR){ printf("send failed"); // break; } char recvBuf[100]; memset(recvBuf, 0, sizeof(recvBuf)); // //接收数据 recv(sockConn, recvBuf, sizeof(recvBuf), 0); printf("%s\n", recvBuf); closesocket(sockConn); closesocket(sockSrv); WSACleanup(); system("pause");}
二.客户端代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include#include #pragma comment(lib, "ws2_32.lib")void main(){ //加载套接字 WSADATA wsaData; char buff[1024]; memset(buff, 0, sizeof(buff)); if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("Failed to load Winsock"); return; } SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(5099); addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //创建套接字 SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); if (SOCKET_ERROR == sockClient){ printf("Socket() error:%d", WSAGetLastError()); return; } //向服务器发出连接请求 if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET){ printf("Connect failed:%d", WSAGetLastError()); return; } else { //接收数据 recv(sockClient, buff, sizeof(buff), 0); printf("%s\n", buff); } //发送数据 char *buffSend = "hello, this is a Client...."; send(sockClient, buffSend, strlen(buffSend) + 1, 0); printf("%d", strlen(buffSend) + 1); //关闭套接字 closesocket(sockClient); WSACleanup(); system("pause");}
怕某些小白不懂我详细说说运行,运行时先开服务端,在开客户端运行 ,也就是开两个cPP文件分别运行,两个cpp各是一个小项目代码 不要放在一起
这里的127.0.0.1是代表本地的地址,你们想实现两机交互就用对方的地址。
代码很简单,想要直接拿去,接下来直接看运行结果:
UDP:
windows下UDP服务端代码
#include#include #include #include using namespace std;#pragma comment(lib,"ws2_32.lib")#define BUFFER_SIZE 1024int main(){ WSADATA WSAData; char receBuf[BUFFER_SIZE]; char Response[BUFFER_SIZE]; if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) { printf("初始化失败"); exit(1); } SOCKET sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockServer == INVALID_SOCKET) { printf("Failed socket() \n"); return 0; } SOCKADDR_IN addr_Server; //服务器的地址等信息 addr_Server.sin_family = AF_INET; addr_Server.sin_port = htons(4567); addr_Server.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(sockServer, (SOCKADDR*)&addr_Server, sizeof(addr_Server)) == SOCKET_ERROR) { //服务器与本地地址绑定 printf("Failed socket() %d \n", WSAGetLastError()); return 0; } SOCKADDR_IN addr_Clt; int fromlen = sizeof(SOCKADDR); while (true) { int last = recvfrom(sockServer, receBuf, 1024, 0, (SOCKADDR*)&addr_Clt, &fromlen); if (last>0) { //判断接收到的数据是否为空 receBuf[last] = '\0';//给字符数组加一个'\0',表示结束了。不然输出有乱码 if (strcmp(receBuf, "bye") == 0) { cout << " 客户端不跟我聊天了..." << endl; closesocket(sockServer); return 0; } else { printf("接收到数据(%s):%s\n", inet_ntoa(addr_Clt.sin_addr), receBuf); } } cout << "回复客户端消息:"; Response = “”; cin >> Response; //给客户端回复消息 sendto(sockServer, Response, strlen(Response), 0, (SOCKADDR*)&addr_Clt, sizeof(SOCKADDR)); } closesocket(sockServer); WSACleanup(); return 0;}
windows下UDP客户端端代码
#include#include #include #include using namespace std;#pragma comment(lib,"ws2_32.lib")# define BUFFER_SIZE 1024 //缓冲区大小int main(){ SOCKET sock_Client; //客户端用于通信的Socket WSADATA WSAData; char receBuf[BUFFER_SIZE]; //发送数据的缓冲区 char sendBuf[BUFFER_SIZE]; //接受数据的缓冲区 if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) { printf("初始化失败!"); return -1; } //初始化 sock_Client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//创建客户端用于通信的Socket SOCKADDR_IN addr_server; //服务器的地址数据结构 addr_server.sin_family = AF_INET; addr_server.sin_port = htons(4567);//端口号为4567 addr_server.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //127.0.0.1为本电脑IP地址 SOCKADDR_IN sock; int len = sizeof(sock); while (true) { cout << "请输入要传送的数据:"; cin >> sendBuf; sendto(sock_Client, sendBuf, strlen(sendBuf), 0, (SOCKADDR*)&addr_server, sizeof(SOCKADDR)); //int last=recv(sock_Client, receBuf, strlen(receBuf), 0); // (调用recv和recvfrom都可以) int last = recvfrom(sock_Client, receBuf, strlen(receBuf), 0, (SOCKADDR*)&sock, &len); if (last>0) { receBuf[last] = '\0'; //给字符数组加一个'\0',表示结束了。不然输出有乱码 if (strcmp(receBuf, "bye") == 0) { cout << "服务器不跟我聊天了..." << endl;//当服务器发来bye时,关闭socket closesocket(sock_Client); break; } else { printf("接收到数据:%s\n", receBuf); } } } closesocket(sock_Client); WSACleanup(); return 0;}
注:以下代码需在LINUX下运行 gcc什么的都可以
LINUX下:
一.TCP
linux下TCP服务端代码:
#include#include #include #include #include int main(int argc, char *argv[]) { int server_sockfd;//服务器端套接字 int client_sockfd;//客户端套接字 int len; struct sockaddr_in my_addr; //服务器网络地址结构体 struct sockaddr_in remote_addr; //客户端网络地址结构体 int sin_size; char buf[BUFSIZ]; //数据传送的缓冲区 memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零 my_addr.sin_family=AF_INET; //设置为IP通信 my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上 my_addr.sin_port=htons(8000); //服务器端口号 /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/ if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) { perror("socket error"); return 1; } /*将套接字绑定到服务器的网络地址上*/ if(bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) { perror("bind error"); return 1; } /*监听连接请求--监听队列长度为5*/ if(listen(server_sockfd,5)<0) { perror("listen error"); return 1; }; sin_size=sizeof(struct sockaddr_in); /*等待客户端连接请求到达*/ if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0) { perror("accept error"); return 1; } printf("accept client %s/n",inet_ntoa(remote_addr.sin_addr)); len=send(client_sockfd,"Welcome to my server/n",21,0);//发送欢迎信息 /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/ while((len=recv(client_sockfd,buf,BUFSIZ,0))>0) { buf[len]='\0'; printf("%s/n",buf); if(send(client_sockfd,buf,len,0)<0) { perror("write error"); return 1; } } /*关闭套接字*/ close(client_sockfd); close(server_sockfd); return 0; }
linux下TCP客户端代码:
#include#include #include #include #include int main(int argc, char *argv[]) { int client_sockfd; int len; struct sockaddr_in remote_addr; //服务器端网络地址结构体 char buf[BUFSIZ]; //数据传送的缓冲区 memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零 remote_addr.sin_family=AF_INET; //设置为IP通信 remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址 remote_addr.sin_port=htons(8000); //服务器端口号 /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/ if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) { perror("socket error"); return 1; } /*将套接字绑定到服务器的网络地址上*/ if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0) { perror("connect error"); return 1; } printf("connected to server/n"); len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息 buf[len]='\0'; printf("%s",buf); //打印服务器端信息 /*循环的发送接收信息并打印接收信息(可以按需发送)--recv返回接收到的字节数,send返回发送的字节数*/ while(1) { printf("Enter string to send:"); scanf("%s",buf); if(!strcmp(buf,"quit") break; len=send(client_sockfd,buf,strlen(buf),0); len=recv(client_sockfd,buf,BUFSIZ,0); buf[len]='/0'; printf("received:%s/n",buf); } /*关闭套接字*/ close(client_sockfd); return 0; }
二.UDP
linux下UDP服务端代码
#include#include #include #include #include int main(int argc, char *argv[]) { int server_sockfd; int len; struct sockaddr_in my_addr; //服务器网络地址结构体 struct sockaddr_in remote_addr; //客户端网络地址结构体 int sin_size; char buf[BUFSIZ]; //数据传送的缓冲区 memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零 my_addr.sin_family=AF_INET; //设置为IP通信 my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上 my_addr.sin_port=htons(8000); //服务器端口号 /*创建服务器端套接字--IPv4协议,面向无连接通信,UDP协议*/ if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0) { perror("socket error"); return 1; } /*将套接字绑定到服务器的网络地址上*/ if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) { perror("bind error"); return 1; } sin_size=sizeof(struct sockaddr_in); printf("waiting for a packet.../n"); /*接收客户端的数据并将其发送给客户端--recvfrom是无连接的*/ if((len=recvfrom(server_sockfd,buf,BUFSIZ,0,(struct sockaddr *)&remote_addr,&sin_size))<0) { perror("recvfrom error"); return 1; } printf("received packet from %s:/n",inet_ntoa(remote_addr.sin_addr)); buf[len]='/0'; printf("contents: %s/n",buf); /*关闭套接字*/ close(server_sockfd); return 0; }
linux下UDP客户端代码
#include#include #include #include #include int main(int argc, char *argv[]) { int client_sockfd; int len; struct sockaddr_in remote_addr; //服务器端网络地址结构体 int sin_size; char buf[BUFSIZ]; //数据传送的缓冲区 memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零 remote_addr.sin_family=AF_INET; //设置为IP通信 remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址 remote_addr.sin_port=htons(8000); //服务器端口号 /*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/ if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0) { perror("socket error"); return 1; } strcpy(buf,"This is a test message"); // 发送的内容 printf("sending: '%s'/n",buf); sin_size=sizeof(struct sockaddr_in); /*向服务器发送数据包*/ if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0) { perror("recvfrom"); return 1; } /*关闭套接字*/ close(client_sockfd); return 0; }
最后说一句啦。本网络编程入门系列博客是连载学习的,有兴趣的可以看我博客其他篇。。。。
参考博客:https://blog.csdn.net/u012234115/article/details/54142273
参考博客:https://www.cnblogs.com/zwj-199306231519/p/9067618.html
参考博客:https://blog.csdn.net/u010223072/article/details/46771047