C语言网络编程:TCP客户端实现
生活随笔
收集整理的這篇文章主要介紹了
C语言网络编程:TCP客户端实现
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文章目錄
- 客戶端通信步驟
- 為什么客戶端沒有bind和listen
- 客戶端connect函數介紹
- 局域網內客戶端和服務器通信代碼實例
客戶端通信步驟
根據基本TCP網絡通信編程模型
我們可以知道客戶端的實現主要有幾個步驟
- socket創建客戶端通信的套接字文件,并指定通信的協議族和數據類型
- 使用connect主動向服務器發起連接請求,與服務器的accept實現三次握手建立連接。連接成功之后客戶端可以通過socket返回的文件描述符進行通信 ,服務器使用accept返回的通信文件描述符進行通信
- 使用send/recv和服務器 發送接收數據進行通信
- 通信結束之后斷開連接
close或者shutdown
為什么客戶端沒有bind和listen
關于bind函數的功能,是為了綁定ip和端口號,即通信使用固定的ip和端口號進行通信。這樣的要求僅僅是針對tcp服務器而言的,對于多個客戶端來說,這樣的bind操作是不需要的。如果指定bind也是可以通信,但是并不合理。在通信過程中只需要使用自己的ip和默認分配的端口號即可。客戶端默認分配的端口范圍為49152~65535
關于listen函數,對于客戶端程序,它永遠是主動連接請求的發起者,沒有被動監聽別人連接的需求。所以這里不需要將主動通信文件描述符轉換為被動通信文件描述符。
客戶端connect函數介紹
- 頭文件:
#include <sys/socket.h> - 函數使用:
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen); - 函數功能: 向服服務器發起主動連接請求
- 返回值:成功0;失敗 -1,并設置errno。
- 函數參數:
a.sockfdsocket返回的套接字文件描述符
b.addrlenaddr結構體的大小
c.addr用于設置想要連接的服務器的ip和端口號
注意:如果局域網內部通信,則ip和端口號可以直接填寫服務器的ip和端口號,但是如果是跨網通信,IP必須是服務器所在路由器的公網ip
為了方便設置struct sockaddr結構體的內容,我們同樣使用sockaddr_in結構體進行設置struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(6789); addr.sin_addr.s_addr = inet_addr("127.0.0.1");cfd = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));
局域網內客戶端和服務器通信代碼實例
一下代碼是在本機測試,大家可以測試局域網內以及不同網段的通信,不過需要注意IP地址的填寫,跨網段通信中ip的填寫需要保證是服務器所在路由器的公網ip。
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>#define IP "192.168.102.174"
#define PORT 7000typedef struct data {char name[30];unsigned int num;
}Data;
void print_err(char *str, int line, int err_no) {printf("%d, %s :%s\n",line,str,strerror(err_no));_exit(-1);
}
int cfd = -1;//子線程,處理數據的接收
void *receive(void *pth_arg) {int ret = 0;Data stu_data = {0};while(1) {bzero(&stu_data, sizeof(stu_data));ret = recv(cfd, &stu_data, sizeof(stu_data),0); if (-1 == ret) {print_err("recv failed",__LINE__,errno);}else if (ret > 0)printf("student number = %d student name = %s \n",ntohl(stu_data.num),stu_data.name);}
}//接收SIGINT信號,從而關閉通信描述符。
void sig_fun(int signo) {if (signo == SIGINT) {//close(cfd);shutdown(cfd,SHUT_RDWR);_exit(0);}
}int main()
{int skfd = -1, ret = -1;skfd = socket(AF_INET, SOCK_STREAM, 0);if ( -1 == skfd) {print_err("socket failed",__LINE__,errno);}struct sockaddr_in addr;addr.sin_family = AF_INET; //設置tcp協議族addr.sin_port = htons(PORT); //設置端口號addr.sin_addr.s_addr = inet_addr(IP); //設置ip地址ret = bind(skfd, (struct sockaddr*)&addr, sizeof(addr));if ( -1 == ret) {print_err("bind failed",__LINE__,errno);}ret = listen(skfd, 3);if ( -1 == ret ) {print_err("listen failed", __LINE__, errno);}/*被動監聽客戶端發起的tcp連接請求,三次握手后連接建立成功*/struct sockaddr_in caddr = {0};int csize = sizeof(caddr);cfd = accept(skfd, (struct sockaddr*)&caddr, &csize);if (-1 == cfd) {print_err("accept failed", __LINE__, errno);}//打印連接后接收到的客戶端發送過來的ip和端口號printf("cport = %d, caddr = %s\n", ntohs(caddr.sin_port),inet_ntoa(caddr.sin_addr));創建子線程用于接收數據pthread_t id;ret = pthread_create(&id,NULL,receive,NULL);if (-1 == ret) print_err("pthread_create failed", __LINE__, errno);//用于處理信號函數,處理SIGINT的信號signal(SIGINT,sig_fun);Data std_data = {0};while (1) {bzero(&std_data, sizeof(std_data));printf("stu name:\n");scanf("%s",std_data.name);//對于int型的需要將主機端序轉換為網絡端序,這里轉成long型。printf("stu num:\n");scanf("%d",&std_data.num);std_data.num = htonl(std_data.num);將數據std_data強制類型轉換后發送ret = send(cfd, (void *)&std_data,sizeof(std_data),0);if ( -1 == ret) {print_err("accept failed", __LINE__, errno);} }return 0;
}
client.c
客戶端的代碼和服務器端主要差異在連接上,客戶端只需要初始化一下socket即可主動發起連接請求,服務端在初始化socket之后需要綁定ip和端口號,同時將主動通信的文件描述符轉為被動通信的文件描述符。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>#define IP "192.168.102.174"
#define PORT 7000typedef struct data {char name[30];unsigned int num;
}Data;
void print_err(char *str, int line, int err_no) {printf("%d, %s :%s\n",line,str,strerror(err_no));_exit(-1);
}
int skfd = -1;
void *receive(void *pth_arg) {int ret = 0;Data stu_data = {0};while(1) {bzero(&stu_data, sizeof(stu_data));ret = recv(skfd, &stu_data, sizeof(stu_data),0); if (-1 == ret) {print_err("recv failed",__LINE__,errno);}else if (ret > 0)printf("student number = %d student name = %s \n",ntohl(stu_data.num),stu_data.name);}
}void sig_fun(int signo) {if (signo == SIGINT) {//close(cfd);shutdown(skfd,SHUT_RDWR);_exit(0);}
}int main()
{int ret = -1;skfd = socket(AF_INET, SOCK_STREAM, 0);if ( -1 == skfd) {print_err("socket failed",__LINE__,errno);}/*這里需要填寫服務器的端口號和IP地址,此時客戶端會自己分配端口號和自己的IP發送給服務器*/struct sockaddr_in addr;addr.sin_family = AF_INET; //設置tcp協議族addr.sin_port = htons(PORT); //設置服務器端口號addr.sin_addr.s_addr = inet_addr(IP); //設置服務器ip地址,如果是跨網,則需要填寫服務器所在路由器的公網ipret = connect(skfd,(struct sockaddr *)&addr,sizeof(addr));if ( -1 == ret ) {print_err("connect failed", __LINE__, errno);}pthread_t id;pthread_create(&id,NULL,receive,NULL);Data std_data = {0};while (1) {bzero(&std_data, sizeof(std_data));printf("stu name:\n");scanf("%s",std_data.name);printf("stu num:\n");scanf("%d",&std_data.num);std_data.num = htonl(std_data.num);ret = send(skfd, (void *)&std_data,sizeof(std_data),0);if ( -1 == ret) {print_err("send failed", __LINE__, errno);} }return 0;
}
編譯gcc server.c -o server -pthread
gcc client.c -o client -pthread
運行如下:
總結
以上是生活随笔為你收集整理的C语言网络编程:TCP客户端实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 香奈儿口红多少钱啊?
- 下一篇: qq女生签名个性网