采用TCP协议的C/S架构实现
以下例子虽然实现了多个客户端访问,但是仍然是阻塞模式,即一个客户访问时会阻塞其他客户的连接和访问)。
模块封装
将一些通用的代码全部封装起来,以后直接调用函数即可:
通过网络封装代码头文件为:tcp_net_socket.h
。
#ifndef __TCP__NET__SOCKET__H
#define __TCP__NET__SOCKET__H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
extern int tcp_init(const char * ip, int port);
extern int tcp_accept(int sfd);
extern int tcp_connect(const char * ip, int port);
extern void signalhandler(void);
#endif
具体的通用函数封装如下:(tcp_net_socket.c
)
#include "tcp_net_socket.h"
// 初始化操作,包括以下操作
// 1. 建立监听 socket
// 2. 把本地 IP 和端口信息,绑定到监听socket;
// 3. 进行监听
int tcp_init(const char * ip, int port)
{
// 建立本地socket
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfd) {
perror("socket");
exit(-1);
}
// bind:把本地 Socket 绑定到主机地址上
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(struct sockaddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr(ip);
if(-1 == bind(sfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr))) {
perror("bind");
close(sfd);
exit(-1);
}
// listen:监听是否有客户端的请求
if(-1 == listen(sfd, 10)) {
perror("listen");
close(sfd);
exit(-1);
}
return sfd;
}
// 用于服务器端的接收,主要工作如下:
// 1. 接受来自客户端的请求,并返回与客户端对应的新socket;
int tcp_accept(int sfd)
{
// 建立新socket
struct sockaddr_in clientaddr;
bzero(&clientaddr, sizeof(struct sockaddr));
int addrlen = sizeof(struct sockaddr);
int new_fd = accept(sfd, (struct sockaddr *)&clientaddr, &addrlen);
if(-1 == new_fd) {
perror("accept");
close(sfd);
exit(-1);
}
printf("%s %d success connect\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
return new_fd; // 返回客户端的 Socket
}
// connect:客户但主动向服务器发起连接请求。
int tcp_connect(const char * ip, int port)
{
// 建立socket
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfd) {
perror("socket");
exit(-1);
}
// 填写服务器IP和端口信息到socket的
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(struct sockaddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr(ip);
// 向服务器发起连接请求
if(-1 == connect(sfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr))) {
perror("connect");
close(sfd);
exit(-1);
}
return sfd; // 返回服务器的 Socket
}
// 忽略中断和退出信号
// 让服务器在按下 Ctrl + C 或Ctrl + \时不会退出
void signalhandler(void)
{
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGINT);
sigaddset(&sigSet, SIGQUIT);
sigprocmask(SIG_BLOCK, &sigSet, NULL);
}
服务器的实现
服务器端:tcp_net_server.c
#include "tcp_net_socket.h"
int main(int argc, char * argv[])
{
if(argc < 3) {
printf("usage:./servertcp ip port\n");
exit(-1);
}
// 注册信号
signalhandler();
// 初始化socket
int sfd = tcp_init(argv[1], atoi(argv[2]));
// 用while循环表示可以与多个客户端接收和发送,但仍然是阻塞式的
while(1) {
// 接收客户端的连接
int cfd = tcp_accept(sfd);
char buf[512] = {0};
// 与客户端进行通信
if(-1 == recv(cfd, buf, sizeof(buf), 0)) {
perror("recv");
close(cfd);
close(sfd);
exit(-1);
}
printf("%s: %d: 正在处理...\n", __FILE__, __LINE__);
puts(buf);
usleep(5000000);
printf("%s: %d: 处理完成...\n", __FILE__, __LINE__);
if(-1 == send(cfd, "hello, world", 12, 0)) {
perror("send");
close(cfd);
close(sfd);
exit(-1);
}
close(cfd);
}
close(sfd);
return 0;
}
客户端的实现
客户端:tcp_net_client.c
#include "tcp_net_socket.h"
int main(int argc, char * argv[])
{
if (argc < 3) {
printf("usage: ./clientaddr ip port\n");
exit(-1);
}
// 建立连接
int sfd = tcp_connect(argv[1], atoi(argv[2]));
char buf[512] = {0};
send(sfd, "hello", 6, 0);
recv(sfd, buf, sizeof(buf), 0);
puts(buf);
close(sfd);
}
编译执行
# gcc -o tcp_net_server tcp_net_server.c tcp_net_socket.c
# gcc -o tcp_net_client tcp_net_client.c tcp_net_socket.c
# ./tcp_net_server 127.0.1.1 8888
# ./tcp_net_client 127.0.1.1 8888
编译成共享库文件
# gcc -fpic -c tcp_net_socket.c -o tcp_net_socket.o
# gcc -shared tcp_net_socket.o -o libtcp_net_socket.so
# cp lib*.so /lib
# cp tcp_net_socket.h /usr/include
这样头文件包含可以用 include<tcp_net_socket.h>
了,以后再用到的时候就可以直接用。
# gcc -o main main.c -l tcp_net_socket
// 其中 main.c 要包含头文件:include <tcp_net_socket.h>
// main.c 代表实际的源文件