C語(yǔ)言多線(xiàn)程服務(wù)器的實(shí)現(xiàn)實(shí)例
本文基于 C 標(biāo)準(zhǔn)庫(kù)提供的網(wǎng)絡(luò)通信 API,使用 TCP ,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的多線(xiàn)程服務(wù)器 Demo 。
首先要看 API
API
字節(jié)序轉(zhuǎn)換
函數(shù)原型:
#include <arpa/inet.h> uint64_t htonll(uint64_t hostlonglong); uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint64_t ntohll(uint64_t netlonglong); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
h 表示 host, n 表示 network,這些函數(shù)的作用是把主機(jī)的字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)的字節(jié)序(即小端到大端的轉(zhuǎn)變)。
例如:
#include <arpa/inet.h>
#include <stdio.h>
int main()
{
uint32_t host = 0x01020304; // high->low: 01 02 03 04
uint32_t network = htonl(host); // high->low: 04 03 02 01
printf("%p\n", network); // 0x4030201
}
socket
函數(shù)原型:
#include <sys/socket.h> int socket(int domain, int type, int protocol);
建立一個(gè)協(xié)議族為 domain, 協(xié)議類(lèi)型為 type, 協(xié)議編號(hào)為 protocol 的套接字文件描述符。如果函數(shù)調(diào)用成功,會(huì)返回一個(gè)標(biāo)識(shí)這個(gè)套接字的文件描述符,失敗的時(shí)候返回-1。
domain 的取值:
Name Purpose Man page AF_UNIX, AF_LOCAL Local communication unix(7) AF_INET IPv4 Internet protocols ip(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_IPX IPX - Novell protocols AF_NETLINK Kernel user interface device netlink(7) AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) AF_AX25 Amateur radio AX.25 protocol AF_ATMPVC Access to raw ATM PVCs AF_APPLETALK AppleTalk ddp(7) AF_PACKET Low level packet interface packet(7) AF_ALG Interface to kernel crypto API
AF 是 Address Family 的縮寫(xiě),INET 是 Internet 的縮寫(xiě)。某些地方可能會(huì)使用 PF,即 Protocol Family,應(yīng)該是同一個(gè)東西。
type 的取值:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call. SOCK_RAW Provides raw network protocol access. SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering. SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
type 常用的是 STREAM 和 DGRAM ,根據(jù)描述,可以確定前者對(duì)應(yīng) TCP,而后者對(duì)應(yīng) UDP :
SOCK_STREAM套接字表示一個(gè)雙向的字節(jié)流,與管道類(lèi)似。流式的套接字在進(jìn)行數(shù)據(jù)收發(fā)之前必須已經(jīng)連接,連接使用connect()函數(shù)進(jìn)行。一旦連接,可以使用read()或者write()函數(shù)進(jìn)行數(shù)據(jù)的傳輸,流式通信方式保證數(shù)據(jù)不會(huì)丟失或者重復(fù)接收。SOCK_DGRAM和SOCK_RAW這個(gè)兩種套接字可以使用函數(shù)sendto()來(lái)發(fā)送數(shù)據(jù),使用recvfrom()函數(shù)接受數(shù)據(jù),recvfrom()接受來(lái)自制定IP地址的發(fā)送方的數(shù)據(jù)。
對(duì)于第 3 個(gè)參數(shù) protocal,用于指定某個(gè)協(xié)議的特定類(lèi)型,即 type 類(lèi)型中的某個(gè)類(lèi)型。通常某協(xié)議中只有一種特定類(lèi)型,這 樣protocol 參數(shù)僅能設(shè)置為 0 ;但是有些協(xié)議有多種特定的類(lèi)型,就需要設(shè)置這個(gè)參數(shù)來(lái)選擇特定的類(lèi)型。
bind
函數(shù)原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
如果函數(shù)執(zhí)行成功,返回值為 0,否則為 SOCKET_ERROR 。
參數(shù):
sockfd是一個(gè)有效的 socket 描述符(函數(shù)socket()的有效返回值)。addrlen是第二個(gè)參數(shù)addr結(jié)構(gòu)體的長(zhǎng)度。addr是一個(gè)sockaddr結(jié)構(gòu)體指針,包含 IP 和端口等信息。
sockaddr 的結(jié)構(gòu)如下:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
// sa_familt_t 是無(wú)符號(hào)整型,Ubuntu 下是 unsigned short int
sockaddr 的存在是為了統(tǒng)一地址結(jié)構(gòu)的表示方法 ,統(tǒng)一接口函數(shù),使得不同的地址結(jié)構(gòu)可以被 bind(), connect(), recvfrom(), sendto() 等函數(shù)調(diào)用。但一般的編程中并不直接對(duì)此數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作,而使用另一個(gè)與之等價(jià)的數(shù)據(jù)結(jié)構(gòu) sockaddr_in :
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
各字段解析:
sin_family:指代協(xié)議族,在 socket 編程中有 3 個(gè)取值AF_INET, AF_INET6, AF_UNSPEC.sin_port:存儲(chǔ)端口號(hào)(使用網(wǎng)絡(luò)字節(jié)順序)sin_addr:存儲(chǔ)IP地址,使用in_addr這個(gè)數(shù)據(jù)結(jié)構(gòu)sin_zero:是為了讓sockaddr與sockaddr_in兩個(gè)數(shù)據(jù)結(jié)構(gòu)保持大小相同而保留的空字節(jié)。
in_addr 的結(jié)構(gòu)如下:
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;
};
listen
int listen(int sockfd, int backlog);
返回值:無(wú)錯(cuò)誤,返回 0,否則 -1 。
作用:listen 函數(shù)使用主動(dòng)連接套接字變?yōu)楸贿B接套接口,使得一個(gè)進(jìn)程可以接受其它進(jìn)程的請(qǐng)求,從而成為一個(gè)服務(wù)器進(jìn)程。在 TCP 服務(wù)器編程中 listen 函數(shù)把進(jìn)程變?yōu)橐粋€(gè)服務(wù)器,并指定相應(yīng)的套接字變?yōu)楸粍?dòng)連接。
listen 函數(shù)一般在調(diào)用 bind 之后,調(diào)用 accept 之前調(diào)用。
backlog 參數(shù)指定連接請(qǐng)求隊(duì)列的最大個(gè)數(shù)。
accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接受連接請(qǐng)求,成功返回一個(gè)新的套接字描述符 newfd ,失敗返回-1。返回值 newfd 與參數(shù) sockfd 是不同的,newfd 專(zhuān)門(mén)用于與客戶(hù)端的通信,而 sockfd 是專(zhuān)門(mén)用于 listen 的 socket 。
addr 和 addrlen 都是指針,用于接收來(lái)自客戶(hù)端的 addr 的信息。
inet_addr
函數(shù)原型:
in_addr_t inet_addr(const char *cp);
將一個(gè)點(diǎn)分十進(jìn)制的 IP 字符串轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的 uint32_t 。
例子
int main()
{
const char *ip = "127.0.0.1"; // 7f.00.00.01
printf("%p\n", inet_addr(ip)); // 0x0100007f
}
send
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
其中 send(fd, buf, len, flags) 與 sendto(fd, buf, len, flags, NULL, 0) 等價(jià)。
recv
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
其中 recv(fd, buf, len, flags) 與 recvfrom(fd, buf, len, flags, NULL, 0) 等價(jià)。
connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
成功返回 0 ,失敗返回 -1 。
sockfd 是客戶(hù)端進(jìn)程創(chuàng)建的,用于與服務(wù)端通信的 socket ; addr 是目標(biāo)服務(wù)器的 IP 地址和端口。
多線(xiàn)程服務(wù)器
本次實(shí)現(xiàn)的場(chǎng)景如下:
- 客戶(hù)端可以具有多個(gè),客戶(hù)端主動(dòng)連接服務(wù)器,允許每個(gè)客戶(hù)端發(fā)送
msg到服務(wù)器,并接受來(lái)自服務(wù)器的信息。 - 服務(wù)端對(duì)于每個(gè)申請(qǐng)連接到客戶(hù)端,創(chuàng)建一個(gè)線(xiàn)程處理請(qǐng)求。對(duì)于客戶(hù)端發(fā)送過(guò)來(lái)的
msg,然后服務(wù)器把msg加上一些其他字符串,發(fā)送回客戶(hù)端。
server
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#define PORT 8887
#define QUEUE 10
const char *pattern = "Hello, I am the server. Your msg is received, which is: %s";
typedef struct
{
struct sockaddr_in addr;
socklen_t addr_len;
int connectfd;
} thread_args;
void *handle_thread(void *arg)
{
thread_args *targs = (thread_args *)arg;
pthread_t tid = pthread_self();
printf("tid = %u and socket = %d\n", tid, targs->connectfd);
char send_buf[BUFSIZ] = {0}, recv_buf[BUFSIZ] = {0};
while (1)
{
int len = recv(targs->connectfd, recv_buf, BUFSIZ, 0);
printf("[Client %d] %s", targs->connectfd, recv_buf);
if (strcmp("q\n", recv_buf) == 0)
break;
sprintf(send_buf, pattern, recv_buf);
send(targs->connectfd, send_buf, strlen(send_buf), 0);
memset(send_buf, 0, BUFSIZ), memset(recv_buf, 0, BUFSIZ);
}
close(targs->connectfd);
free(targs);
pthread_exit(NULL);
}
int main()
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
printf("server is listening at socket fd = %d\n", listenfd);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
perror("bind error\n");
exit(-1);
}
if (listen(listenfd, QUEUE) == -1)
{
perror("listen error\n");
exit(-1);
}
while (1)
{
thread_args *targs = malloc(sizeof(thread_args));
targs->connectfd = accept(listenfd, (struct sockaddr *)&targs->addr, &targs->addr_len);
// int newfd = accept(sockfd, NULL, NULL);
pthread_t tid;
pthread_create(&tid, NULL, handle_thread, (void *)targs);
pthread_detach(tid);
}
close(listenfd);
}
client
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PORT 8887
const char *target_ip = "127.0.0.1";
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("client socket = %d\n", sockfd);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(target_ip);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
{
perror("connect error\n");
exit(-1);
}
char send_buf[BUFSIZ], recv_buf[BUFSIZ];
while (fgets(send_buf, BUFSIZ, stdin) != NULL)
{
if (strcmp(send_buf, "q\n") == 0)
break;
send(sockfd, send_buf, strlen(send_buf), 0);
printf("[Client] %s\n", send_buf);
recv(sockfd, recv_buf, BUFSIZ, 0);
printf("[Server] %s\n", recv_buf);
memset(send_buf, 0, BUFSIZ), memset(recv_buf, 0, BUFSIZ);
}
close(sockfd);
exit(0);
}
運(yùn)行結(jié)果
編譯:
gcc server.c -o server -lpthread
gcc client.c -o client
先運(yùn)行 server,后運(yùn)行多個(gè) client .

需要注意的是,這里的服務(wù)器,客戶(hù)端都是運(yùn)行在同一機(jī)器上的,所以客戶(hù)端使用的目標(biāo) IP 是 127.0.0.1 ,如果想進(jìn)一步更全面地測(cè)試,應(yīng)該把服務(wù)端運(yùn)行在一個(gè)云服務(wù)器上,然后開(kāi)放 8887 端口,再進(jìn)行測(cè)試。
到此這篇關(guān)于C語(yǔ)言多線(xiàn)程服務(wù)器的實(shí)現(xiàn)實(shí)例的文章就介紹到這了,更多相關(guān)C語(yǔ)言多線(xiàn)程服務(wù)器的實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- c語(yǔ)言多進(jìn)程tcp服務(wù)器示例
- 基于C語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的Web服務(wù)器
- C語(yǔ)言Tinyhttpd服務(wù)器源碼剖析
- 利用C語(yǔ)言實(shí)現(xiàn)http服務(wù)器(Linux)
- C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單回聲服務(wù)器
- 如何利用C語(yǔ)言實(shí)現(xiàn)最簡(jiǎn)單的HTTP服務(wù)器詳解
- 使用C語(yǔ)言來(lái)擴(kuò)展Python程序和Zope服務(wù)器的教程
- 基于C語(yǔ)言實(shí)現(xiàn)的TCP服務(wù)器的流程分析
相關(guān)文章
使用VS2010創(chuàng)建MFC ActiveX工程項(xiàng)目
VS2010開(kāi)發(fā)ActiveX有兩種方法,分別是MFC和ATL。MFC開(kāi)過(guò)起來(lái)比較簡(jiǎn)單,但是最終生成的文件比較大,ATL是專(zhuān)門(mén)用來(lái)開(kāi)發(fā)ActiveX的,但是相對(duì)比較難,必須知道很多原理機(jī)制和API。咱先從MFC開(kāi)發(fā)ActiveX開(kāi)始吧。2015-06-06
詳解C++17中類(lèi)模板參數(shù)推導(dǎo)的使用
自C++17起就通過(guò)使用類(lèi)模板參數(shù)推導(dǎo),只要編譯器能根據(jù)初始值推導(dǎo)出所有模板參數(shù),那么就可以不指明參數(shù),下面我們就來(lái)看看C++17中類(lèi)模板參數(shù)推導(dǎo)的具體使用吧2024-03-03
OpenCV實(shí)現(xiàn)簡(jiǎn)單攝像頭視頻監(jiān)控程序
這篇文章主要為大家詳細(xì)介紹了OpenCV實(shí)現(xiàn)簡(jiǎn)單攝像頭視頻監(jiān)控程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
C語(yǔ)言中求字符串長(zhǎng)度的函數(shù)的幾種實(shí)現(xiàn)方法
這篇文章主要介紹了C語(yǔ)言中求字符串長(zhǎng)度的函數(shù)的幾種實(shí)現(xiàn)方法,需要的朋友可以參考下2018-08-08
VS2019開(kāi)發(fā)簡(jiǎn)單的C/C++動(dòng)態(tài)鏈接庫(kù)并進(jìn)行調(diào)用的實(shí)現(xiàn)
這篇文章主要介紹了VS2019開(kāi)發(fā)簡(jiǎn)單的C/C++動(dòng)態(tài)鏈接庫(kù)并進(jìn)行調(diào)用的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
C++設(shè)計(jì)與實(shí)現(xiàn)ORM系統(tǒng)實(shí)例詳解
這篇文章主要為大家介紹了C++設(shè)計(jì)與實(shí)現(xiàn)ORM系統(tǒng)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
C++利用PCL點(diǎn)云庫(kù)操作txt文件詳解
這篇文章主要為大家詳細(xì)介紹了C++如何利用PCL點(diǎn)云庫(kù)操作txt文件,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2024-01-01

