C语言多线程服务器的实现实例

本文基于 C 标准库提供的网络通信 API,使用 TCP ,实现一个简单的多线程服务器 Demo 。

首先要看 API

API

字节序转换

函数原型:

#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,这些函数的作用是把主机的字节序转换为网络的字节序(即小端到大端的转变)。

例如:

#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 套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用 connect() 函数进行。一旦连接,可以使用 read() 或者 write() 函数进行数据的传输,流式通信方式保证数据不会丢失或者重复接收。
  • SOCK_DGRAMSOCK_RAW 这个两种套接字可以使用函数 sendto() 来发送数据,使用 recvfrom() 函数接受数据,recvfrom() 接受来自制定IP地址的发送方的数据。

对于第 3 个参数 protocal,用于指定某个协议的特定类型,即 type 类型中的某个类型。通常某协议中只有一种特定类型,这 样protocol 参数仅能设置为 0 ;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

bind

函数原型:

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

如果函数执行成功,返回值为 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 :存储端口号(使用网络字节顺序)
  • sin_addr :存储IP地址,使用 in_addr 这个数据结构
  • sin_zero :是为了让 sockaddrsockaddr_in 两个数据结构保持大小相同而保留的空字节。

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 函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在 TCP 服务器编程中 listen 函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

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 字符串转换为网络字节序的 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 是客户端进程创建的,用于与服务端通信的 socket ; addr 是目标服务器的 IP 地址和端口。

多线程服务器

本次实现的场景如下:

  • 客户端可以具有多个,客户端主动连接服务器,允许每个客户端发送 msg 到服务器,并接受来自服务器的信息。
  • 服务端对于每个申请连接到客户端,创建一个线程处理请求。对于客户端发送过来的 msg,然后服务器把 msg 加上一些其他字符串,发送回客户端。

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语言多线程服务器的实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • haskell实现多线程服务器实例代码

    复制代码 代码如下: module Main where import Network.Socketimport Control.Concurrent main :: IO ()main = do    sock <- socket AF_INET Stream 0    bindSocket sock (SockAddrInet 4242 iNADDR_ANY)    listen sock 10240    mainLoop sock mainLoop :: Socket -> IO ()

  • 使用cluster 将自己的Node服务器扩展为多线程服务器

    用nodejs的朋友都有了解,node是单线程的,也就是说跑在8核CPU上,只能使用一个核的算力. 单线程一直是node的一个诟病,但随着0.6版本中引入cluster之后,这个情况则得到了改变,开发人员可以依靠cluster很轻松的将自己的Node服务器扩展为多线程服务器了. 什么是Cluster cluster是node提供的一个多线程库,用户可以使用它来创建多个线程,线程之间共享一个监听端口,当有外部请求这个端口时,cluster会将请求转发到随机线程里.因为每个node线程都会占用几十兆

  • C语言多线程服务器的实现实例

    本文基于 C 标准库提供的网络通信 API,使用 TCP ,实现一个简单的多线程服务器 Demo . 首先要看 API API 字节序转换 函数原型: #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);

  • Java语言多线程终止中的守护线程实例

    Java中线程分为两种类型:用户线程和守护(服务)线程.通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程;不设置则默认为用户线程. 结束单线程用 Thread.interrupt() 方法,多线程结束则需要设置守护线程.当不存在用户线程时,守护线程就会全部终结(可以理解为:守护线程是服务线程,用户线程是被服务线程,用户线程(被服务线程)全都没有了,服务线程便没有存在意义而自动终结) 例子: class StopThr

  • Go语言Echo服务器的方法

    本文实例讲述了Go语言Echo服务器的方法.分享给大家供大家参考.具体如下: 复制代码 代码如下: package main import (     "net"     "io" ) func main() {     serv, e := net.Listen("tcp", ":12345")     if e != nil {         panic(e)     }     defer serv.Close()  

  • C语言数据输入与输出实例详解

    C语言数据输入与输出实例详解 1 概论 C语言提供了跨平台的数据输入输出函数scanf()和printf()函数,它们可以按照指定的格式来解析常见的数据类型,例如整数,浮点数,字符和字符串等等.数据输入的来源可以是文件,控制台以及网络,而输出的终端可以是控制台,文件甚至是网页. 2 数据输出 从第一个c语言程序中,就使用了跨平台的库函数printf实现将一段文字输出到控制台,而实际上,printf()不仅可以将数据按照指定的格式输出到控制台,还可以是网页或者是指定的文件中,printf()函数执

  • 关于C语言多线程pthread库的相关函数说明

    线程相关操作说明 一 pthread_t pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: typedef unsigned long int pthread_t; 它是一个线程的标识符. 二 pthread_create 函数pthread_create用来创建一个线程,它的原型为: extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__at

  • Java Socket编程服务器响应客户端实例代码

    通过输入流来读取客户端信息,相应的时候通过输出流来实现. 服务端类的代码: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; impo

  • python kafka 多线程消费者&手动提交实例

    官方文档:https://kafka-python.readthedocs.io/en/master/apidoc/KafkaConsumer.html import threading import os import sys from kafka import KafkaConsumer, TopicPartition, OffsetAndMetadata from consumers.db_util import * from consumers.json_dispose import *

  • Java创建多线程局域网聊天室实例

    局域网聊天室 在学习了一个学期的java以后,觉得java真是博大精深,彻底放弃了因为c++而轻视java的心态,搞了一个多线程的聊天室,熟悉了一下服务器和客户机的操作. 1.TCP 要实现局域网连接,就必须知道信息传输的原理. 在局域网里面传输的信息都是以包的形式,我使用的TCP包传输数据,TCP包里面封装了IP报文. 下面这句话就是通过一个静态IPV4协议的类得到一个服务器的IP地址. address = InetAddress.getByName("192.168.43.86")

  • Go语言开发保证并发安全实例详解

    目录 什么是并发安全? Mutex 悲观锁 乐观锁 版本号机制 CAS 互斥锁 读写互斥锁 什么是并发安全? 在高并发场景下,进程.线程(协程)可能会发生资源竞争,导致数据脏读.脏写.死锁等问题,为了避免此类问题的发生,就有了并发安全. 这里举一个简单的例子: var data int go func() { data++ }() if data == 0 { fmt.Printf("the value is %v.\n", data) } 在这段代码中 第2行go关键字开启了一个新的

随机推荐