前文我们实现了一个 socket 最小的实现,它只允许一台终端连接到服务器进行数据通信,但这样的程序对我们来说没有什么意义,所以我们一定要实现多个客户端与一个服务端通信交互数据,这样才能真正派上用场,所以本文主要介绍了两种实现多客户端连接的方案,一种是多进程,一种是多线程,两种性能相差无几,但明显多线程在资源方面明显要比多进程消耗要少的多。


公共头文件

/* wrap.h */
#ifndef __WRAP_H__
#define __WRAP_H__

void perr_exit(const char* s);
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
void Bind(int fd, const struct sockaddr* sa, socklen_t salen);
void Connect(int fd, const struct sockaddr* sa, socklen_t salen);
void Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
void Close(int fd);
ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
static ssize_t my_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);

#endif

#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include “wrap.h”

void perr_exit(const char* s)
{
perror(s);
exit(1);
}

int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
int n;

again:
if ( (n = accept(fd, sa, salenptr)) < 0 )
{
if ((errno == ECONNABORTED) (errno == EINTR))
{
goto again;
}
else
{
perr_exit(“accept error”);
}
}
return n;
}

void Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
if ( bind(fd, sa, salen) < 0 )
{
perr_exit(“bind error”);
}
}

void Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
if ( connect(fd, sa, salen) < 0 )
{
perr_exit(“connect error”);
}
}

void Listen(int fd, int backlog)
{
if ( listen(fd, backlog) < 0 )
{
perr_exit(“listen error”);
}
}

int Socket(int family, int type, int protocol)
{
int n = socket(family, type, protocol);
if ( n < 0 )
{
perr_exit(“socket error”);
}
return n;
}

ssize_t Read(int fd, void* ptr, size_t nbytes)
{
ssize_t n;

again:
if ( (n = read(fd, ptr, nbytes)) == -1)
{
if (errno == EINTR)
{
goto again;
}
else
{
return -1;
}
}
return n;
}

ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
ssize_t n;

again:
if ( (n = write(fd, ptr, nbytes)) == -1)
{
if (errno == EINTR)
{
goto again;
}
else
{
return -1;
}
}
return n;
}

void Close(int fd)
{
if (close(fd) == -1)
{
perr_exit(“close error”);
}
}

ssize_t Readn(int fd, void* vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char* ptr;

ptr = vptr;
nleft = n;
while (nleft > 0)
{
if ( (nread = read(fd, ptr, nleft)) < 0 )
{
if (errno == EINTR)
{
nread = 0;
}
else
{
return -1;
}
}
else if (nread == 0)
{
break;
}

nleft -= nread;
ptr += nread;
}
}

ssize_t Writen(int fd, const void* vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char* ptr;

ptr = vptr;
nleft = n;
while (nleft > 0)
{
if ( (nwritten = write(fd, ptr, nleft)) <= 0)
{
if (nwritten < 0 && errno == EINTR)
{
nwritten = 0;
}
else
{
return -1;
}
}

nleft -= nwritten;
ptr += nwritten;
}

return n;
}

static ssize_t my_read(int fd, char* ptr)
{
static int read_cnt;
static char* read_ptr;
static char read_buf[100];

if (read_cnt <= 0)
{
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0 )
{
if (errno == EINTR)
{
goto again;
}
return -1;
}
else if (read_cnt == 0)
{
return 0;
}

read_ptr = read_buf;
}

read_cnt–;
*ptr = *read_ptr++;
return 1;
}

ssize_t Readline(int fd, void* vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;

ptr = vptr;
for (n = 1; n < maxlen; n++)
{
if ( (rc = my_read(fd, &c)) == 1)
{
*ptr++ = c;
if (c == ‘\n’)
{
break;
}
}
else if (rc == 0)
{
*ptr = 0;
return n - 1;
}
else
{
return -1;
}

*ptr = 0;
return n;
}
}

客户端代码(多进程多线程共用)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include “wrap.h”

#define MAXLINE 80
#define SRV_PORT 8000

int main(int argc, char* argv[])
{
struct sockaddr_in srv_addr;
int sock, conn;

sock = Socket(AF_INET, SOCK_STREAM, 0);

bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
inet_pton(AF_INET, “127.0.0.1”, &srv_addr.sin_addr);
srv_addr.sin_port = htons(SRV_PORT);

conn = Connect(sock, (struct sockaddr*)&srv_addr, sizeof(srv_addr));

int len;
char buf[MAXLINE];
while (fgets(buf, sizeof(buf), stdin))
{
Write(sock, buf, strlen(buf));
len = Read(sock, buf, sizeof(buf));
if (len == 0)
{
printf(“server is closed…\n”);
break;
}
else
{
Write(STDOUT_FILENO, buf, len);
}
}

close(sock);
return 0;
}

多进程服务端代码

#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <ctype.h>
#include “wrap.h”

#define MAXLINE 80
#define SRV_PORT 8000

int main(int argc, char* argv[])
{
socklen_t cnt_len;
struct sockaddr_in srv_addr, cnt_addr;

int sock, conn;
int n, i;
pid_t pid;

char buf[MAXLINE];
char str[INET_ADDRSTRLEN];

// 创建socket
sock = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&srv_addr, sizeof(srv_addr));

srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
srv_addr.sin_port = htons(SRV_PORT);

// 绑定ip和端口
Bind(sock, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
// 开始监听
Listen(sock, 20);

printf(“Accepting connections …\n”);
while (1)
{
// 等待接收客户端连接
cnt_len = sizeof(cnt_addr);
conn = Accept(sock, (struct sockaddr*)&cnt_addr, &cnt_len);
printf(“received from %s at PORT %d\n”,
inet_ntop(AF_INET, &cnt_addr.sin_addr, str, sizeof(str)),
ntohs(cnt_addr.sin_port));

// 有新客户端连接创建子进程
pid = fork();
if (pid == 0)
{
// 关闭从父进程继承下来的 socket 文件描述符
Close(sock);
while (1)
{
// 从客户端连接后产生的新文件描述符中读取数据
n = Read(conn, buf, MAXLINE);
// 如果读取返回值为0证明客户端退出了
if (n == 0)
{
printf(“client exit…\n”);
break;
}
// 打印连接的客户端ip和端口号
printf(“%s:%d”,
inet_ntop(AF_INET, &cnt_addr.sin_addr, str, sizeof(str)),
ntohs(cnt_addr.sin_port));

// 将读取到的buf数据打印到屏幕上
Write(STDOUT_FILENO, buf, n);

// 将数据中所有字符转换为大写
for(i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
// 重新发送给客户端
Write(conn, buf, n);
}
// 关闭与客户端的连接
Close(conn);
return 0;
}
// 如果是父进程那么关闭新来的连接文件描述符并且后面什么都不做
else if (pid > 0)
{
Close(conn);
}
else
{
perr_exit(“fork”);
}
}

// 最后关闭socket文件描述符
Close(sock);
return 0;
}

编译多进程程序运行测试

编译客户端:gcc client.c wrap.c -o client 编译服务端:gcc server_fork.c wrap.c -o server_fork 运行效果: 2015-07-11 10:21:15

多线程服务端代码

#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <pthread.h>

#define MAXLINE 80
#define SRV_PORT 8000

struct tag_thread
{
int conn;
struct sockaddr_in sockaddr;
};

void* recv_thread(void* arg)
{
// 将自身线程设置为游离态,无需再次回收
pthread_detach(pthread_self());
// 将参数转换为可识别的结构体
struct tag_thread* new_conn = (struct tag_thread*)arg;

char buf[1024];
char str[INET_ADDRSTRLEN];
int len;
int i;

while (1)
{
// 从传递进来的文件描述符中读取数据
len = Read(new_conn->conn, buf, sizeof(buf));
if (0 == len)
{
pthread_exit(NULL);
}
printf(“recv client data %s:%d\n”,
inet_ntop(AF_INET, &new_conn->sockaddr.sin_addr, str, sizeof(str)),
ntohs(new_conn->sockaddr.sin_port));
// 打印读取到的数据到屏幕
Write(STDOUT_FILENO, buf, len);
// 将读取到的数据转换为大写
for (i = 0; i < strlen(buf); i++)
{
buf[i] = toupper(buf[i]);
}
// 重新发回给客户端
Write(new_conn->conn, buf, len);
}

return (void*)0;
}

int main(int argc, char* argv[])
{
socklen_t cnt_len;
struct sockaddr_in srv_addr, cnt_addr;

int sock, conn;
int n, i;
pid_t pid;
pthread_t tid;

char buf[MAXLINE];
char str[INET_ADDRSTRLEN];

sock = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&srv_addr, sizeof(srv_addr));

srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
srv_addr.sin_port = htons(SRV_PORT);

Bind(sock, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
Listen(sock, 20);

printf(“Accepting connections …\n”);
while (1)
{
struct tag_thread new_conn;
cnt_len = sizeof(cnt_addr);
// 阻塞等待新的客户端连接
conn = Accept(sock, (struct sockaddr*)&cnt_addr, &cnt_len);
// 打印新来的客户端ip和端口
printf(“while received from %s at PORT %d\n”,
inet_ntop(AF_INET, &cnt_addr.sin_addr, str, sizeof(str)),
ntohs(cnt_addr.sin_port));

// 设置要传递给线程函数的结构体内容
new_conn.conn = conn;// 新连接的文件描述符
new_conn.sockaddr = cnt_addr;// 新连接的属性结构体

// 创建线程
pthread_create(&tid, NULL, recv_thread, (void*)&new_conn);
}

Close(sock);
return 0;
}

编译多线程程序运行测试

编译客户端:gcc client.c wrap.c -o client 编译服务端:gcc server_thread.c wrap.c -o server_thread -l pthread 运行效果: 2015-07-11 10:42:55