原文作者:aircraft
原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html
本網絡編程入門系列博客是連載學習的,有興趣的可以看我博客其他篇。。。。c++ 網絡編程課設入門超詳細教程 ---目錄
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 <WinSock2.h> #include <stdio.h> #include <stdlib.h> #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 <WinSock2.h> #include <stdio.h> #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<winsock2.h> #include<stdio.h> #include<string.h> #include<iostream> using namespace std; #pragma comment(lib,"ws2_32.lib") #define BUFFER_SIZE 1024 int 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<winsock2.h> #include<stdio.h> #include<string.h> #include<iostream> 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 <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> 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 <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> 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 <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> 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 <stdio.h>
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> 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
若有興趣交流分享技術,可關注本人公眾號,里面會不定期的分享各種編程教程,和共享源碼,諸如研究分享關於c/c++,python,前端,后端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識