亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C語言多線程服務器的實現實例

 更新時間:2021年02月25日 09:56:49   作者:sinkinben  
這篇文章主要介紹了C語言多線程服務器的實現實例,文章用實例講解的很清楚,有對這方面不太懂的同學可以參考下

本文基于 C 標準庫提供的網絡通信 API,使用 TCP ,實現一個簡單的多線程服務器 Demo 。

首先要看 API

API

字節(jié)序轉換

函數原型:

#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,這些函數的作用是把主機的字節(jié)序轉換為網絡的字節(jié)序(即小端到大端的轉變)。

例如:

#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

函數原型:

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

建立一個協議族為 domain, 協議類型為 type, 協議編號為 protocol 的套接字文件描述符。如果函數調用成功,會返回一個標識這個套接字的文件描述符,失敗的時候返回-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 的縮寫,INET 是 Internet 的縮寫。某些地方可能會使用 PF,即 Protocol Family,應該是同一個東西。

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 常用的是 STREAMDGRAM ,根據描述,可以確定前者對應 TCP,而后者對應 UDP :

  • SOCK_STREAM 套接字表示一個雙向的字節(jié)流,與管道類似。流式的套接字在進行數據收發(fā)之前必須已經連接,連接使用 connect() 函數進行。一旦連接,可以使用 read() 或者 write() 函數進行數據的傳輸,流式通信方式保證數據不會丟失或者重復接收。
  • SOCK_DGRAMSOCK_RAW 這個兩種套接字可以使用函數 sendto() 來發(fā)送數據,使用 recvfrom() 函數接受數據,recvfrom() 接受來自制定IP地址的發(fā)送方的數據。

對于第 3 個參數 protocal,用于指定某個協議的特定類型,即 type 類型中的某個類型。通常某協議中只有一種特定類型,這 樣protocol 參數僅能設置為 0 ;但是有些協議有多種特定的類型,就需要設置這個參數來選擇特定的類型。

bind

函數原型:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

如果函數執(zhí)行成功,返回值為 0,否則為 SOCKET_ERROR 。

參數:

  • sockfd 是一個有效的 socket 描述符(函數 socket() 的有效返回值)。
  • addrlen 是第二個參數 addr 結構體的長度。
  • addr 是一個 sockaddr 結構體指針,包含 IP 和端口等信息。

sockaddr 的結構如下:

struct sockaddr {
 sa_family_t sa_family;
 char sa_data[14];
};
// sa_familt_t 是無符號整型,Ubuntu 下是 unsigned short int

sockaddr 的存在是為了統一地址結構的表示方法 ,統一接口函數,使得不同的地址結構可以被 bind(), connect(), recvfrom(), sendto() 等函數調用。但一般的編程中并不直接對此數據結構進行操作,而使用另一個與之等價的數據結構 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 :指代協議族,在 socket 編程中有 3 個取值 AF_INET, AF_INET6, AF_UNSPEC .
  • sin_port :存儲端口號(使用網絡字節(jié)順序)
  • sin_addr :存儲IP地址,使用 in_addr 這個數據結構
  • sin_zero :是為了讓 sockaddrsockaddr_in 兩個數據結構保持大小相同而保留的空字節(jié)。

in_addr 的結構如下:

typedef uint32_t in_addr_t;
struct in_addr{
 in_addr_t s_addr;
};

listen

int listen(int sockfd, int backlog);

返回值:無錯誤,返回 0,否則 -1 。

作用:listen 函數使用主動連接套接字變?yōu)楸贿B接套接口,使得一個進程可以接受其它進程的請求,從而成為一個服務器進程。在 TCP 服務器編程中 listen 函數把進程變?yōu)橐粋€服務器,并指定相應的套接字變?yōu)楸粍舆B接。

listen 函數一般在調用 bind 之后,調用 accept 之前調用。

backlog 參數指定連接請求隊列的最大個數。

accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

接受連接請求,成功返回一個新的套接字描述符 newfd ,失敗返回-1。返回值 newfd 與參數 sockfd 是不同的,newfd 專門用于與客戶端的通信,而 sockfd 是專門用于 listen 的 socket 。

addraddrlen 都是指針,用于接收來自客戶端的 addr 的信息。

inet_addr

函數原型:

in_addr_t inet_addr(const char *cp);

將一個點分十進制的 IP 字符串轉換為網絡字節(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) 等價。

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) 等價。

connect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

成功返回 0 ,失敗返回 -1 。

sockfd 是客戶端進程創(chuàng)建的,用于與服務端通信的 socket ; addr 是目標服務器的 IP 地址和端口。

多線程服務器

本次實現的場景如下:

  • 客戶端可以具有多個,客戶端主動連接服務器,允許每個客戶端發(fā)送 msg 到服務器,并接受來自服務器的信息。
  • 服務端對于每個申請連接到客戶端,創(chuàng)建一個線程處理請求。對于客戶端發(fā)送過來的 msg,然后服務器把 msg 加上一些其他字符串,發(fā)送回客戶端。

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);
}

運行結果

編譯:

gcc server.c -o server -lpthread
gcc client.c -o client

先運行 server,后運行多個 client .

需要注意的是,這里的服務器,客戶端都是運行在同一機器上的,所以客戶端使用的目標 IP 是 127.0.0.1 ,如果想進一步更全面地測試,應該把服務端運行在一個云服務器上,然后開放 8887 端口,再進行測試。

到此這篇關于C語言多線程服務器的實現實例的文章就介紹到這了,更多相關C語言多線程服務器的實現內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C++ QT智能指針的使用詳解

    C++ QT智能指針的使用詳解

    這篇文章主要介紹了C++ QT智能指針的使用,Qt是一個跨平臺的C++框架,主要用來開發(fā)圖形用戶界面程序,也可以開發(fā)不帶界面的命令行程序,下面我們來了解QT智能指針是如何使用的
    2023-12-12
  • 關于C語言文件操作方法

    關于C語言文件操作方法

    這篇文章主要介紹了關于C語言文件操作方法的相關資料,需要的朋友可以參考下
    2018-03-03
  • C++中的Lambda表達式及表達式語句

    C++中的Lambda表達式及表達式語句

    這篇文章主要介紹了C++中的Lambda表達式及表達式語句,表達式這個概念在C++中屬于比較細節(jié)的知識了,很多時候我們只用知道怎么用,對于編譯器內部怎么處理我們并不關心;并且關于左值和右值這個概念,也是C++比較深的一個小知識點,需要的朋友可以參考一下
    2021-12-12
  • C語言詳解strcmp函數的分析及實現

    C語言詳解strcmp函數的分析及實現

    strcmp函數語法為“int strcmp(char *str1,char *str2)”,其作用是比較字符串str1和str2是否相同,如果相同則返回0,如果不同,前者大于后者則返回1,否則返回-1
    2022-05-05
  • 一篇文章徹底弄懂C++虛函數的實現機制

    一篇文章徹底弄懂C++虛函數的實現機制

    C++中的虛函數的作用主要是實現了多態(tài)的機制,基類定義虛函數,子類可以重寫該函數,在派生類中對基類定義的虛函數進行重寫時,需要在派生類中聲明該方法為虛方法,這篇文章主要給大家介紹了關于如何通過一篇文章徹底弄懂C++虛函數的實現機制,需要的朋友可以參考下
    2021-06-06
  • 關于C++中二分法詳解

    關于C++中二分法詳解

    大家好,本篇文章主要講的是關于C++中二分法詳解,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • C++設計模式之狀態(tài)模式

    C++設計模式之狀態(tài)模式

    這篇文章主要介紹了C++設計模式之狀態(tài)模式,本文講解了什么是狀態(tài)模式、狀態(tài)模式的使用場合、狀態(tài)模式的實現代碼等內容,需要的朋友可以參考下
    2014-10-10
  • C++實現哈希散列表的示例

    C++實現哈希散列表的示例

    本文主要介紹了C++實現哈希散列表的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • 探討:C++實現鏈式二叉樹(用非遞歸方式先序,中序,后序遍歷二叉樹)

    探討:C++實現鏈式二叉樹(用非遞歸方式先序,中序,后序遍歷二叉樹)

    本篇文章是對用C++實現鏈式二叉樹(用非遞歸方式先序,中序,后序遍歷二叉樹)的方法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • c語言_構建一個靜態(tài)二叉樹實現方法

    c語言_構建一個靜態(tài)二叉樹實現方法

    下面小編就為大家?guī)硪黄猚語言_構建一個靜態(tài)二叉樹實現方法。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05

最新評論