macos下简单的socket服务器+客户端
TCP客户端服务器编程模型:
服务器:
- 调用socket函数创建套接字
- 调用bind绑定本地IP和端口
- 调用listen启动监听(准备好接收客户端链接的队列)
- 调用accept从已连接队列中提取第一个连接。(如果没有,会阻塞。)
- 调用I/O函数(read/write)与客户端通讯
- 调用close关闭套接字。(多个套接字)
客户端:
- 调用socket创建套接字
- 调用connect连接服务器
- 调用I/O函数(read/write)与服务器通讯
- 调用close关闭套接字
Linux下和Mac下代码一样的,可能有头文件不太一样,用man命令进去查看即可。
功能:
- 客户端连到服务器,服务器打印连接的客户端IP和端口,返回给客户端当前服务器时间。
- 服务器加了一个信号捕获函数,ctrl+c停止服务器进程。
- 如果不写客户端,使用telnet 127.0.0.1 8888 依然可以收到服务器返回的时间字符串。
服务器代码 time_tcp_server.c:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#define SERVER_PORT 8888
#define LISTEN_QUEUE_SISE 10
int socketfd;
void signal_handler(int signo)
{
printf("this serveice close\n");
close(socketfd);
exit(1);
}
void out_clientinfo(const struct sockaddr_in* outsockaddr)
{
char ipstr[16];
memset(ipstr, 0, sizeof(ipstr));
// 将地址从网络字节序转换为点分十进制
inet_ntop(AF_INET, &outsockaddr->sin_addr.s_addr, ipstr, sizeof(ipstr));
printf("Connected by %s(%d)\n", ipstr, ntohs(outsockaddr->sin_port));
}
void dosomething(int fd)
{
//获取系统当前时间
long t = time(0);
char* times = ctime(&t);
size_t size = strlen(times)*sizeof(char);
//将时间写回到客户端
if(write(fd, times, size) != size)
{
perror("write to client error");
}
}
int main(int argc, char const *argv[])
{
if (signal(SIGINT, signal_handler) == SIG_ERR)
{
perror("signal error");
exit(1);
}
// 1 sokect
// AF_INET ipv4
// SOCK_STREAM tcp
if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket error");
exit(1);
}
// 2 bind 绑定本地地址和端口
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;//ipv4
serveraddr.sin_port = htons(SERVER_PORT); //端口
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//响应任意网卡的请求
if(bind(socketfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind error");
exit(1);
}
// 3 listen 启动监听 通知系统接受来自客户端的连接 准备好连接队列
if(listen(socketfd, LISTEN_QUEUE_SISE) < 0)
{
perror("listen error");
exit(1);
}
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
while(1)
{
// 4 accept 从队列拿出第一个
// clientaddr获取客户端的地址信息,是传出参数
int clientfd = accept(socketfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
if (clientfd < 0)
{
perror("accept error");
continue;
}
// 5 read/write
out_clientinfo(&clientaddr);
dosomething(clientfd);
// 6 close
close(clientfd);
}
// 6 close
return 0;
}
|
客户端代码time_tcp_client:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#define SERVER_PORT 8888
#define SERVER_IP 127.0.0.1
int main(int argc, char const *argv[])
{
//1 创建socket
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd < 0)
{
perror("socket error");
exit(1);
}
//2 connect
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
if(connect(socketfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0 )
{
perror("connect error");
exit(1);
}
//3 read write
char buf[1024];
//read是阻塞函数 如果服务器没有下发消息,会一直阻塞在这里,知道收到消息。
if (read(socketfd, buf, sizeof(buf)) > 0)
{
printf("%s",buf);
}
//4 close
close(socketfd);
return 0;
}
|
本例只是简单的处理,服务器返回客户端一个时间,然后关闭了socket。
如果要进行双向通信,服务器势必要调用read函数,而read默认阻塞,那么如果客户端不向服务器发送数据,则主线程一直阻塞,其它客户端无法连接成功。这就需要处理高并发问题。
服务器高并发处理的三种方式
- 多进程 *https://www.cnblogs.com/lan0725/p/11634709.html*
- 多线程 https://www.cnblogs.com/lan0725/p/11639142.html
- I/O多路复用