C语言实现socket网络编程及多线程编程
生活随笔
收集整理的這篇文章主要介紹了
C语言实现socket网络编程及多线程编程
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文章目錄
- 一.概述
- 二.TCP socket網絡編程
- 1.server端程序實現(tcp_server.cpp)
- 2.client端程序實現(tcp_client.cpp)
- 3.編譯與執行
- 三.UDP socket網絡編程
- 1.server端程序實現(udp_server.cpp)
- 2.client端程序實現(udp_client.cpp)
- 3.編譯與執行
一.概述
??socket網絡編程在網絡通信中至關重要,因此本文結合socket網絡編程和linux多線程編程在linux環境下實現網絡通信。實現思路大致如下:server服務端監聽某個socket端口等待客戶端client連接,在客戶端連接成功后創建一個線程(pthread_create)來處理該連接,在主線程中監聽客戶端的連接,在子線程中處理每個socket連接。
二.TCP socket網絡編程
1.server端程序實現(tcp_server.cpp)
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h> #include <iostream> #include <pthread.h> //使用多線程頭文件 #include <netinet/in.h>using namespace std; //使用標準空間//注意:在socket服務器編程當中有多個文件描述符 //其中socket返回的文件描述符sockfd是用來監聽用的,不能讀寫,而accept返回的文件描述符clifd是用來連接的操作的文件描述符。可以讀寫操作 /* //sockaddr_in結構體(用來管理server或者client的socket信息)定義: struct sockaddr_in{__SOCKADDR_COMMON (sin_); //ipv4 or ipv6in_port_t sin_port; //Port number.struct in_addr sin_addr; //Internet address.// Pad to size of `struct sockaddr'. unsigned char sin_zero[sizeof (struct sockaddr) -__SOCKADDR_COMMON_SIZE -sizeof (in_port_t) -sizeof (struct in_addr)];}; */ #define SERV_PORT 8010 //服務器端口 #define SERV_ADDR "192.168.1.23" //服務器ip #define BACKLOG 100 //服務器最多可以監聽的客戶端數量(最多排隊數量)int client_num = 0; //連接的客戶端計數//線程1處理函數 void *client_func1(void *clifd_recv) {int ret = -1;char recv_buf[200]; //定義接收緩存區char send_buf[200]; //定義發送緩存區int clifd = *(int *)clifd_recv; //創建新線程首先獲取傳入的客戶端描述符clifdprintf("有新的客戶端 %d 連接成功,子線程1開始工作\r\n",clifd);//5.在連接成功后使用recv函數來接收數據//函數原型:ssize_t recv(int socket, void *buffer, size_t length, int flags);while(1){ret = recv(clifd, recv_buf, sizeof(recv_buf), 0); //接收客戶端發送來的數據,注意此處使用的網絡描述符是clifdif(ret < 1){cout<<"客戶端 "<<clifd<<" 斷開了連接"<<endl;cout<<"關閉連接并退出"<<endl;close(clifd); //關閉accept文件描述符clifdclient_num--; //當前服務的客戶端數量加一pthread_exit(NULL); //退出當前的線程}cout<<"收到客戶端 "<<clifd<<" 發送的數據:len= "<<ret<<" buf= "<<recv_buf<<endl;memset(recv_buf,0,sizeof(recv_buf)); //清空接收緩存區//6.使用send函數發生數據strcpy(send_buf,"hello client1!");ret = send(clifd, send_buf, strlen(send_buf), 0);cout<<"發送了"<<ret<<"個數據"<<endl;memset(send_buf,0,sizeof(send_buf)); //清空接收緩存區} }//線程2處理函數 void *client_func2(void *clifd_recv) {int ret = -1;char recv_buf[200]; //定義接收緩存區char send_buf[200]; //定義發送緩存區int clifd = *(int *)clifd_recv; //創建新線程首先獲取傳入的客戶端描述符clifdprintf("有新的客戶端 %d 連接成功,子線程2開始工作\r\n",clifd);//5.在連接成功后使用recv函數來接收數據//函數原型:ssize_t recv(int socket, void *buffer, size_t length, int flags);while(1){ ret = recv(clifd, recv_buf, sizeof(recv_buf), 0); //接收客戶端發送來的數據,注意此處使用的網絡描述符是clifdif(ret < 1){cout<<"客戶端 "<<clifd<<" 斷開了連接"<<endl;cout<<"關閉連接并退出"<<endl;close(clifd); //關閉accept文件描述符clifdclient_num--; //當前服務的客戶端數量加一pthread_exit(NULL); //退出當前的線程}cout<<"收到客戶端 "<<clifd<<" 發送的數據:len= "<<ret<<" buf= "<<recv_buf<<endl;memset(recv_buf,0,sizeof(recv_buf)); //清空接收緩存區//6.使用send函數發生數據strcpy(send_buf,"hello client2!!");ret = send(clifd, send_buf, strlen(send_buf), 0);cout<<"發送了"<<ret<<"個數據"<<endl;memset(send_buf,0,sizeof(send_buf)); //清空接收緩存區} }//線程3處理函數 void *client_func3(void *clifd_recv) {int ret = -1;char recv_buf[200]; //定義接收緩存區char send_buf[200]; //定義發送緩存區int clifd = *(int *)clifd_recv; //創建新線程首先獲取傳入的客戶端描述符clifdprintf("有新的客戶端 %d 連接成功,子線程3開始工作\r\n",clifd);//5.在連接成功后使用recv函數來接收數據//函數原型:ssize_t recv(int socket, void *buffer, size_t length, int flags);while(1){ ret = recv(clifd, recv_buf, sizeof(recv_buf), 0); //接收客戶端發送來的數據,注意此處使用的網絡描述符是clifdif(ret < 1){cout<<"客戶端 "<<clifd<<" 斷開了連接"<<endl;cout<<"關閉連接并退出"<<endl;close(clifd); //關閉accept文件描述符clifdclient_num--; //當前服務的客戶端數量加一pthread_exit(NULL); //退出當前的線程}cout<<"收到客戶端 "<<clifd<<" 發送的數據:len= "<<ret<<" buf= "<<recv_buf<<endl;memset(recv_buf,0,sizeof(recv_buf)); //清空接收緩存區//6.使用send函數發生數據strcpy(send_buf,"hello client3!!!");ret = send(clifd, send_buf, strlen(send_buf), 0);cout<<"發送了"<<ret<<"個數據"<<endl;memset(send_buf,0,sizeof(send_buf)); //清空接收緩存區} }int main(int argc,char **argv) {int ret = -1;int sockfd = -1; //定義socket網絡文件描述符int clifd = -1; //定義accept網絡文件描述符pthread_t th = -1; //定義一個多線程創建句柄struct sockaddr_in servaddr={0}; //服務器sockaddr_in定義成ipv4類型的服務器ip結構體(ipv6是sockaddr_inv6)struct sockaddr_in cliaddr={0}; //客戶端sockaddr_insocklen_t address_len = 0; //客戶端長度//1.首先使用socket函數創建網絡文件描述符(類似于文件IO中的open函數)//函數原型:int socket(int domain, int type, int protocol); sockfd = socket(AF_INET, SOCK_STREAM, 0); //ipv4,TCP,系統自動選擇protocolif(sockfd < 0){cout<<"創建socket文件描述符失敗"<<endl;_exit(-1);}cout<<"sockfd="<<sockfd<<endl;//注意:由TCP套接字狀態TIME_WAIT引起在結束本次會話后close立刻開啟下次會話會Bind失敗。//該狀態在套接字關閉后約保留 2 到 4 分鐘。在 TIME_WAIT 狀態退出之后,套接字被刪除,該地址才能被重新綁定而不出問題。//因此下面兩句話的加入可以解決這個問題int on = 1;ret = setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );//2.使用bind函數綁定socket文件描述符和相關參數//函數原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); servaddr.sin_family = AF_INET; //定義servaddr的domain地址族為ipv4servaddr.sin_port = htons(SERV_PORT); //定義servaddr的portnum為SERV_PORT(8010),host to net shortservaddr.sin_addr.s_addr = inet_addr(SERV_ADDR); //定義servaddr的address為SERV_ADDR(192.168.1.23) person----->u32//servaddr.sin_addr.s_addr = INADDR_ANY; //監聽該電腦的所有IPmemset(servaddr.sin_zero,0,sizeof(servaddr.sin_zero)); //設置servaddr的sin_zero區域為0ret = bind(sockfd, (const struct sockaddr *)&servaddr,sizeof(servaddr)); //使用bind函數綁定socket文件描述符和相關參數if(ret < 0){cout<<"bind函數綁定socket文件描述符失敗"<<endl;_exit(-1);}cout<<"bind綁定成功"<<endl;//3.使用listen函數進行接收監聽(監聽當前設置的ip地址下的端口號port)//函數原型:int listen(int socket, int backlog);ret = listen(sockfd, BACKLOG); //sockfd,等待列表,最多可以排隊排BACKLOG(100)個if(ret < 0){cout<<"listen監聽失敗"<<endl;_exit(-1);}cout<<"listen監聽成功,等待客戶端連接:"<<endl;while(1){//4.使用accept函數阻塞等待客戶端連接,注意:會阻塞!!!//函數原型:int accept(int socket, struct sockaddr *restrict address,socklen_t *restrict address_len);address_len = sizeof(struct sockaddr); //給client_len賦值clifd = accept(sockfd, (struct sockaddr *)&cliaddr,&address_len); //此時會阻塞在這監聽客戶端連接if(clifd < 0){cout<<"accept連接客戶端失敗"<<endl;_exit(-1);}cout<<"listen連接客戶端成功,clifd ="<<clifd<<endl;cout<<"客戶端端口號port= "<<ntohs(cliaddr.sin_port)<<endl;cout<<"客戶端ip= "<<inet_ntoa(cliaddr.sin_addr)<<endl;//在收到客戶端連接成功后創建一個新的線程,在線程里進行數據收發client_num++; //當前服務的客戶端數量加1switch(client_num){case 1:ret = pthread_create(&th, NULL, client_func1, (void *)(&clifd)); //創建一個線程client_func,將客戶端描述符clifd作為參數傳遞給多線程處理函數break;case 2:ret = pthread_create(&th, NULL, client_func2, (void *)(&clifd));break;case 3:ret = pthread_create(&th, NULL, client_func3, (void *)(&clifd));break;default:cout<<"連接的客戶端數量超過3臺,停止服務"<<endl;break;}if(ret == 0){printf("創建線程 %d 成功,有新的客戶端 %d 連接成功,線程創建成功\r\n",(int)th,clifd);pthread_detach(th); //在線程創建成功后使用pthread_detach分離子線程,這樣就可以在子線程退出后自動回收子線程資源了}else{printf("線程創建失敗\r\n");_exit(-1);}}cout<<"所有線程全部關閉,退出"<<endl;close(sockfd); //關閉socket文件描述符return 0; }2.client端程序實現(tcp_client.cpp)
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h> #include <iostream>using namespace std; //使用標準空間 /* //sockaddr_in結構體定義: struct sockaddr_in{__SOCKADDR_COMMON (sin_); //ipv4 or ipv6in_port_t sin_port; //Port number.struct in_addr sin_addr; //Internet address.// Pad to size of `struct sockaddr'. unsigned char sin_zero[sizeof (struct sockaddr) -__SOCKADDR_COMMON_SIZE -sizeof (in_port_t) -sizeof (struct in_addr)];}; */ #define SERV_PORT 8010 //服務器端口 #define SERV_ADDR "192.168.1.23" //服務器ip #define CLI_ADDR "192.168.1.50" //服務器開放給我們的ip地址 char recv_buf[1000]; //定義接收緩存區 char send_buf[1000]; //定義發送緩存區 int main(int argc,char **argv) {int ret = -1;int sockfd = -1; //定義網絡文件描述符struct sockaddr_in servaddr={0}; //服務器sockaddr_in定義成ipv4類型的服務器ip結構體(ipv6是sockaddr_inv6)//1.首先使用socket函數創建網絡文件描述符(類似于文件IO中的open函數)//函數原型:int socket(int domain, int type, int protocol); sockfd = socket(AF_INET, SOCK_STREAM, 0); //ipv4,TCP,系統自動選擇protocolif(sockfd < 0){cout<<"創建socket文件描述符失敗"<<endl;_exit(-1);}cout<<"sockfd="<<sockfd<<endl;//2.使用connect函數連接服務器//函數原型:int connect(int socket, const struct sockaddr *address,socklen_t address_len);servaddr.sin_family = AF_INET; //定義servaddr的domain地址族為ipv4servaddr.sin_port = htons(SERV_PORT); //定義servaddr的portnum為SERV_PORT(8010),host to net shortservaddr.sin_addr.s_addr = inet_addr(SERV_ADDR); //定義servaddr的address為SERV_ADDR(192.168.1.23) person----->u32ret = connect(sockfd, (const struct sockaddr *)&servaddr,sizeof(servaddr));if(ret < 0){cout<<"客戶端connect服務器失敗"<<endl;_exit(-1);}cout<<"客戶端connect服務器成功"<<endl;//下面客戶端和服務器互相收發while(1){//6.使用send函數發生數據cout<<"請輸入要發送給服務器的內容:";cin >> send_buf;if(!strncmp(send_buf,"+++",3))break;ret = send(sockfd, send_buf, strlen(send_buf), 0);cout<<"發送了"<<ret<<"個數據"<<endl;memset(send_buf,0,sizeof(send_buf)); //清空接收緩存區ret = recv(sockfd, recv_buf, sizeof(recv_buf), 0); //接收客戶端發送來的數據,注意此處使用的網絡描述符是clifdif(ret < 1){cout<<"服務器斷開了連接"<<endl;break;}cout<<"收到服務器發送的數據:len= "<<ret<<" buf= "<<recv_buf<<endl;memset(recv_buf,0,sizeof(recv_buf)); //清空接收緩存區}cout<<"關閉連接并退出"<<endl;close(sockfd); //關閉socket文件描述符return 0; }3.編譯與執行
g++ tcp_server.cpp -o server -pthread g++ tcp_client.cpp -o client #arm-linux-gnueabihf-g++ tcp_server.cpp -o server #arm-linux-gnueabihf-g++ tcp_client.cpp -o client 其中g++編譯器編譯出的文件在x86架構處理器運行,arm-linux-gnueabihf-g++編譯出的文件在ARM架構處理器運行。另注意用到多線程pthread時編譯需要鏈接pthread庫。 編譯完成后,先后運行服務器程序./server和客戶端程序./client程序,進行數據交互。三.UDP socket網絡編程
1.server端程序實現(udp_server.cpp)
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h> #include <iostream> #include <netinet/in.h>using namespace std; //使用標準空間 //在使用UDP網絡編程的時候,由于UDP非面向連接,因此不需要listen和accept。因此可以簡單總結UDP server端的程序編寫流程如下: //1.socket創建fd //2.bind綁定server端的socket信息 //3.直接調用recvfrom函數接收來自客戶端的消息,同時讀取客戶端的信息并保存在cliaddr結構體中,以便下次發送消息的時候可以準確找到對方 //4.調用sendto函數給客戶端返回信息,需要用到剛剛接收的cliaddr信息(因為服務器可能被多個客戶端連接,因此不知道發給誰,只能根據cliaddr信息使用sendto發送)#define SERV_PORT 8010 //服務器端口 #define SERV_ADDR "192.168.1.23" //服務器ip #define BACKLOG 100 //服務器最多可以監聽的客戶端數量(最多排隊數量)int main(int argc,char **argv) {int ret = -1;int sockfd = -1; //定義socket網絡文件描述符int recv_len; //接收數據長度int send_len; //發送數據長度char recv_buf[200]; //定義接收緩存區char send_buf[200]; //定義發送緩存區struct sockaddr_in servaddr={0}; //服務器sockaddr_in定義成ipv4類型的服務器ip結構體(ipv6是sockaddr_inv6)struct sockaddr_in cliaddr={0}; //客戶端sockaddr_insocklen_t address_len = 0; //客戶端長度//1.首先使用socket函數創建網絡文件描述符(類似于文件IO中的open函數)//函數原型:int socket(int domain, int type, int protocol); sockfd = socket(AF_INET, SOCK_DGRAM, 0); //ipv4,UDP,系統自動選擇protocolif(sockfd < 0){cout<<"創建socket文件描述符失敗"<<endl;_exit(-1);}cout<<"sockfd="<<sockfd<<endl;//注意:由TCP套接字狀態TIME_WAIT引起在結束本次會話后close立刻開啟下次會話會Bind失敗。//該狀態在套接字關閉后約保留 2 到 4 分鐘。在 TIME_WAIT 狀態退出之后,套接字被刪除,該地址才能被重新綁定而不出問題。//因此下面兩句話的加入可以解決這個問題int on = 1;ret = setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );//2.使用bind函數綁定socket文件描述符和相關參數//函數原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); servaddr.sin_family = AF_INET; //定義servaddr的domain地址族為ipv4servaddr.sin_port = htons(SERV_PORT); //定義servaddr的portnum為SERV_PORT(8010),host to net shortservaddr.sin_addr.s_addr = inet_addr(SERV_ADDR); //定義servaddr的address為SERV_ADDR(192.168.1.23) person----->u32//servaddr.sin_addr.s_addr = INADDR_ANY; //監聽該電腦的所有IPmemset(servaddr.sin_zero,0,sizeof(servaddr.sin_zero)); //設置servaddr的sin_zero區域為0ret = bind(sockfd, (const struct sockaddr *)&servaddr,sizeof(servaddr)); //使用bind函數綁定socket文件描述符和相關參數if(ret < 0){cout<<"bind函數綁定socket文件描述符失敗"<<endl;_exit(-1);}cout<<"bind綁定成功"<<endl;while(1){//3.使用recvfrom函數接收客戶端發送的數據,返回接收數據長度//入口參數:fd,reve_buf,recv_len,flag,clinet_addr,clinet_lenaddress_len = sizeof(struct sockaddr);recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf),0,(struct sockaddr *)&cliaddr,&address_len);if(recv_len > 0) //判斷接收到的數據大于0{recv_buf[recv_len] = '\0'; //添加結束符cout<<"客戶端端口號port= "<<ntohs(cliaddr.sin_port)<<endl;cout<<"客戶端ip= "<<inet_ntoa(cliaddr.sin_addr)<<endl;printf("從客戶端接收到 %d 個數據,數據是:%s\r\n",recv_len,recv_buf); //打印出接收到的數據memset(recv_buf,0,sizeof(recv_buf));}//4.使用sendto函數發生數據address_len = sizeof(struct sockaddr); //地址長度strcpy(send_buf,"hello client!");send_len = sendto(sockfd, send_buf, strlen(send_buf), 0,(const struct sockaddr *)&cliaddr, address_len);if(send_len > 0){cout<<"發送了 "<<send_len<<" 個數據"<<endl;memset(send_buf,0,sizeof(send_buf)); //清空接收緩存區}}cout<<"關閉連接并退出"<<endl;close(sockfd); //關閉socket文件描述符return 0; }2.client端程序實現(udp_client.cpp)
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h> #include <iostream>using namespace std; //使用標準空間//在使用UDP網絡編程的時候,由于UDP非面向連接。因此可以簡單總結UDP client端的程序編寫流程如下: //1.socket創建fd //2.確定server的socket信息并寫入servaddr中 //3.直接調用sendto函數給服務器發送消息,其中要用到servaddr信息 //4.調用recv函數接收來自服務器的消息,(因為客戶端知道服務器的信息,因此直接recv就可以,而不用recvfrom)#define SERV_PORT 8010 //服務器端口 #define SERV_ADDR "192.168.1.23" //服務器ip #define CLI_ADDR "192.168.1.50" //定義客戶端地址,一般用不到int main(int argc,char **argv) {int ret = -1;int sockfd = -1; //定義網絡文件描述符int address_len = 0; //目的的長度struct sockaddr_in servaddr={0}; //用來存放服務器的socket相關信息int send_len; //發送數據長度int recv_len; //接收數據長度char recv_buf[1000]; //定義接收緩存區char send_buf[1000]; //定義發送緩存區//1.首先使用socket函數創建網絡文件描述符(類似于文件IO中的open函數)//函數原型:int socket(int domain, int type, int protocol); sockfd = socket(AF_INET, SOCK_DGRAM, 0); //ipv4,UDP,系統自動選擇protocolif(sockfd < 0){cout<<"創建socket文件描述符失敗"<<endl;_exit(-1);}cout<<"sockfd="<<sockfd<<endl;//2.然后確定server的socket信息并寫入servaddr中servaddr.sin_family = AF_INET; //定義servaddr的domain地址族為ipv4servaddr.sin_port = htons(SERV_PORT); //定義servaddr的portnum為SERV_PORT(8010),host to net shortservaddr.sin_addr.s_addr = inet_addr(SERV_ADDR); //定義servaddr的address為SERV_ADDR(192.168.1.23) person----->u32//下面客戶端和服務器互相收發while(1){cout<<"請輸入要發送給服務器的內容:";cin >> send_buf;if(!strncmp(send_buf,"+++",3))break;//3.使用sendto函數給服務器發生數據//入口參數:fd,send_buf,send_len,flag,server_addr,server_lenaddress_len = sizeof(struct sockaddr); //地址長度send_len = sendto(sockfd, send_buf, strlen(send_buf), 0,(const struct sockaddr *)&servaddr, address_len);if(send_len > 0){cout<<"發送了 "<<send_len<<" 個數據"<<endl;memset(send_buf,0,sizeof(send_buf)); //清空接收緩存區}//4.使用recv函數接收來自服務器的數據ret = recv(sockfd, recv_buf, sizeof(recv_buf), 0); //接收客戶端發送來的數據,注意此處使用的網絡描述符是clifdif(ret < 1){cout<<"服務器斷開了連接"<<endl;break;}cout<<"收到服務器發送的數據:len= "<<ret<<" buf= "<<recv_buf<<endl;memset(recv_buf,0,sizeof(recv_buf)); //清空接收緩存區}cout<<"關閉連接并退出"<<endl; //實際從未建立過連接close(sockfd); //關閉socket文件描述符return 0; }3.編譯與執行
g++ udp_server.cpp -o server g++ udp_client.cpp -o client #arm-linux-gnueabihf-g++ udp_server.cpp -o server #arm-linux-gnueabihf-g++ udp_client.cpp -o client 其中g++編譯器編譯出的文件在x86架構處理器運行,arm-linux-gnueabihf-g++編譯出的文件在ARM架構處理器運行。另注意用到多線程pthread時編譯需要鏈接pthread庫。執行說明,由于UDP不面向連接,因此接收和發送的時候都需要知道對方的信息,而服務器可以接收多個客戶端的數據,因此在執行測試時可以打開一個server程序和多個client程序,建立一對多的連接,且彼此之間交互互不影響。
總結
以上是生活随笔為你收集整理的C语言实现socket网络编程及多线程编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第三十七期:刷脸支付叫好不叫座,为啥消费
- 下一篇: 广西2021高考成绩位次查询,2020年