什么是字节序:CPU对内存数据的存取顺序
大端字节序&小端字节序
大端 :低位保存在高地址 小端:低位保存在低地址
78 12
56 34
34 56
12 78
网络字节序:采用大端字节序传输,防止出现大小端机器解读数据出错的情况
主机字节序:主机字节序指的是机器具体的字节序
主机是大端:认为主机字节序是大端
主机是小端:认为主机字节序是小端
网络设计需要继续转发之前都要将主机字节序转换成网络字节序
网络数据接收之前都需要将网络字节序转换成主机字节序
1、网络规定采用大端字节序作为网络字节序
2、路由设备或者交换机需要对网络数据进行分用到网络层,以获得“目的ip地址”,而这些设备在继续分用的时候默认是按照网络字节序进行分用的
无连接:当udp的客户端想要给udp服务端发送数据的时候,只需要知道服务端的ip和端口,就可以直接发送
引申含义:在发送数据之前是不知道服务端的状态信息的
不可靠:不报证udp的数据一定到达对端机器
面向数据报:udp数据是整条数据接收和发送的
有连接:连接双方在发送数据之前,需要进行连接(指需要提前告知,沟通连接的信息)
可靠传输:保证数据是有序且可靠到达对端
面向字节流:上一次和下一次的数据是没有明显的数据边界的
cs模型(一般使用):client(客户端)-server(服务端)
bs模型:浏览器-服务器
客户端 服务端
1、创建套接字 1、创建套接字
2、绑定地址信息(不推荐自己绑定) 2、绑定地址信息:ip地址、端口信息
3、先由客户端向服务端发送数据
3、再由服务端向客户端回复数据
4、关闭套接字 4、关闭套接字
int socket(int domain, int type, int protocol);
domain:指定当前的地址域,相当于指定网络层到底使用什么协议
AF_INET:ipv4网络(默认选择)
AF_INET6:ipv6网络
AF_UNIX:本地域套接字
type:创建套接字的类型
SOCK_DGRAM:用户数据报套接字 =》udp协议
SOCK_STREAM:流式套接字 =》tcp协议
protocol:
0:表示使用套接字类型默认的协议
IPPROTO_UDP:17
IPPROTO_TCP:6
返回值:
返回桃姐字描述符,套接字描述符本质上就是一个文件描述符
成功:大于等于0
失败:小于0
创建套接字的原因在于想在内核当中创建一块与协议相对应的缓冲区
需要包含一个头文件
#include
如果protocol一栏中不使用默认的0则需要包含一个头文件
#include
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/socket.h>
4 #include <netinet/in.h>
5
6 int main()
7 {
8 int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
9 if(sockfd < 0)
10 {
11 perror("socket fail\n");
12 return 0;
13 }
14
15 printf("sockfd : %d\n", sockfd);
16 while(1)
17 {
18 sleep(1);
19 }
20 return 0;
21 }
会发现在闪烁,此时闪烁的原因是它指向了内核中的缓冲区
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket函数返回的套接字描述符
将创建出来的套接字和网卡、端口进行绑定
addr:地址信息结构
struct sockaddr---》通用地址信息结构
struct sockaddr {
sa_family_t sa_family; //地址域信息,占用两个字节
//地址域信息决定了当前使用寿命网络层协议
char sa_data[14]; //本质上任何网络程序都不会直接填充这个14个字节的字符组,
//定义第二个参数总共占用14个字节
}
———————————————
| 2字节地址域信息 |
| 一一 |
| ↑ |
| 14字节地址数据 |
| ↓ |
| 一一 |
———————————————
ipv4:
vim /usr/include/netinet/in.h
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); //地址域信息:2字节
in_port_t sin_port; /* Port number. */ //端口信息:2字节
struct in_addr sin_addr; /* Internet address. */ //ipv4版本的ip地址:4字节
/* Pad to size of `struct sockaddr'. */ //填充剩下的部分为0:8字节
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
———————————————
| 2字节地址域信息 |
| 2字节端口信息 |
| 4字节 |
| ip地址 |
| 一一 |
| ↑ |
| 8字节填充为0 |
| ↓ |
| 一一 |
———————————————
地址域信息:网络协议栈通过该信息,来判断传递进来的是哪一个数据结构,判断出来之后,就知道后面的字节该如何解析
本地域套接字:进行本地进程间通信的一种手段
struct sockaddr_un
———————————————
| 2字节地址域信息 |
| 一一 |
| ↑ |
| 108字节路径名 |
| ↓ |
| 一一 |
———————————————
addrlen:地址信息结构的长度,防止越界
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
spckfd:套接字描述符
buf:想要发送的数据
len:要发送数据的长度
flags:
0:阻塞发送
dest_addr:目标主机的地址信息结构(ip,port(端口))
addrlen:目标主机地址信息结构的长度
返回值:
成功:返回具体发送的字节数量
失败:返回-1
客户端为什么不推荐绑定地址信息?
本质上是不想让那个客户端程序将端口写死
即不想让客户端在启动的时候都是绑定一个端口(一个端口只能被一个协议当中的一个进程所绑定)
客户端1绑定了端口,本机在启动客户端2的时候会绑定失败,因为端口已经被客户端1拿走了
客户端没有主动的绑定端口,udp客户端在调用sendto的时候,会自动绑定(操作系统分配)一个空闲的端口
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:套接字描述符
buf:将数据接收到buf当中
len:最大可以接收多大的数据
flags:
0:阻塞接收
src_addr:接收的数据来源的主机地址信息结构
addrlen:输入输出型参数
输入:在接收之前准备的对端地址信息结构的长度
输出:实际接收回来的地址信息长度
返回值:
接收到:接收的字节数量
未接收:-1
eg:
char* buf[1024] = {0}; <--数据接收的位置
struct sockaddr_in src_addr; <--定义一个具体结构体例如ipv4
recvfrom(sockfd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&src_addr, )
↑预留\0 ↑传递结构体的地址,需要强转为(struct sockaddr*)
接收完后src_addr中有ip和port,即发送数据的主机的ip地址和端口
close(int sockfd);
接口1:h(ost)ton(et)l(long) --> htonl 4字节
uint32_t htonl(uint32_t hostlong);
如果主机是大端机调用接口则什么都不干,如果主机是小端机则会将数据转换成小端再返回
接口2:htons(hort) --> htons 2字节
uint16_t htons(uint16_t hostshort);
网络字节序转换成为主机字节序
接口1:ntohl 4字节
uint32_t ntohl(uint32_t netlong);
接口2:ntohs 2字节
uint16_t ntohs(uint16_t netshort);
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
首先测试一下绑定地址信息
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/socket.h> //包含了各种数据结构
4 #include <netinet/in.h>
5 #include <arpa/inet.h>
6
7 int main()
8 {
9 //创建套接字
10 int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
11 if(sockfd < 0)
12 {
13 perror("socket failed\n");
14 return 0;
15 }
16 //绑定
17 struct sockaddr_in addr;//定义一个ipv4的端口
18 addr.sin_family = AF_INET;//地址域,告诉操作系统内核传递的是哪种结构体
19 addr.sin_port = htons(12345);//传递需要监听的端口,需要转换成为网络字节序
20 addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //ip地址保存的变量
21 //0.0.0.0表示当前机器任一一块网卡的ip地址
22 //127.0.0.1表示当前机器本地回环网卡地址
23
24 int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
25 if(ret < 0)
26 {
27 perror("bind failed\n");
28 return 0;
29 }
30
31 while(1)
32 {
33 sleep(1);
34 }
35 return 0;
36 }
可以看到确实是创建成功了,可以使用 netstat -anp | grep [端口] 来查看端口使用情况
udp 表示当前12345端口使用的是udp协议
0.0.0.0:12345 代表当前监听的ip地址与端口
0.0.0.0:* 表示接收从任一ip和任一端口来的数据
如果此时再启动一次程序,会报错,因为端口已经被占用了
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <sys/socket.h> //包含了各种数据结构
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7
8 int main()
9 {
10 //创建套接字
11 int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
12 if(sockfd < 0)
13 {
14 perror("socket failed\n");
15 return 0;
16 }
17 //绑定
18 struct sockaddr_in addr;//定义一个ipv4的端口
19 addr.sin_family = AF_INET;//地址域,告诉操作系统内核传递的是哪种结构体
20 addr.sin_port = htons(12345);//传递需要监听的端口,需要转换成为网络字节序
21 addr.sin_addr.s_addr = inet_addr("10.0.8.14");//ip地址保存的变量
22 //0.0.0.0表示当前机器任一一块网卡的ip地址
23 //127.0.0.1表示当前机器本地回环网卡地址
24
25 int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
26 if(ret < 0)
27 {
28 perror("bind failed\n");
29 return 0;
30 }
31
32 while(1)
33 {
34 //接收部分
35
36 char buf[1024] = {0};
37 struct sockaddr_in peer_addr;
38 socklen_t peer_addrlen = sizeof(peer_addr);
39 ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer_addr, &peer_addrlen);
40 if(recv_size < 0)
41 {
42 continue;//接收失败继续接收
43 }
44 //成功
45 printf("i am server, i recv %s, from %s:%d\n", buf, inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
46
47 //发送部分
48
49 memset(buf, '\0', sizeof(buf));//清空buf
50 sprintf(buf, "hello, client %s:%d, i am server\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
51 //写入到buf中
52 //发送
53 sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&peer_addr, sizeof(peer_addr));
54 }
55 return 0;
56 }
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <sys/socket.h> //包含了各种数据结构
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7
8 int main()
9 {
10 //创建套接字
11 int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
12 if(sockfd < 0)
13 {
14 perror("socket failed\n");
15 return 0;
16 }
17
18 //不用绑定
19
20 while(1)
21 {
22 //发送数据
23 char buf[1024] = "i am client1";
24
25 struct sockaddr_in dest_addr;
26 dest_addr.sin_family = AF_INET;
27 dest_addr.sin_port = htons(12345);//需要发送到的端口
28 dest_addr.sin_addr.s_addr = inet_addr("10.0.8.14");
29 int se = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
30 if(se < 0)
31 {
32 continue;
33 }
34
35 //接收数据
36 memset(buf, '\0', sizeof(buf));
37 ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, NULL, NULL);//发送时已经获得了服务端的信息
38 if(recv_size < 0)
39 {
40 perror("recvfrom failed\n");
41 continue;
42 }
43
44 printf("server say: %s\n", buf);
45 }
46 return 0;
47 }
客户端 服务端
1、创建套接字 1、创建套接字
流式套接字 创建流式套接字(SOCK_STREAM)
2、不推荐绑定地址信息 2、绑定地址信息
ip/port
3、发起连接 3、监听
①告诉操作系统内核,可以接收客户端发起的连接请求
②监听新连接的到来
三次握手建立连接
4、接收新连接
。。。
互相发送数据
。。。
四次挥手断开连接
完成三次握手后即建立连接完成
三次握手的过程是在内核(网络协议栈)中完成的,建立连接的过程不需要程序员参与
第一次:客户端给服务端发送连接请求
第二次:服务端回复客户端的连接请求并发送连接请求
第三次:客户端回复服务端的连接请求,连接建立
断开连接需要四次挥手
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
sockfd:套接字描述符
backlog:已完成连接队列的大小
三次握手中:
客户端向服务端发送一个名为SYN的数据包,表示发起连接
服务端收到请求后向客户端发送SYN+ACK两个数据包,其中SYN代表服务端回复也想建立连接,ACK是回复上一个SYN的
客户端向服务端回复一个ACK数据包,用于回复服务端发送的SYN数据包
此时内核中有两个队列,一个叫未完成连接队列,一个叫已完成连接队列
已完成连接队列:
1、已经完成三次握手等待被accept的连接
2、连接状态为“连接建立”的连接
为完成连接队列:
没有完成三次握手的连接
backlog影响了服务端并发接收连接的能力
假设backlog是1,此时未完成队列中的数量为2,即此时只能有一个能进入已完成连接队列,在进入已完成连接队列后该新
建立的连接被拿走,又有一个连接可以进入已完成连接队列,即吞吐量为1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:套接字描述符
addr:服务端的地址信息结构(服务端的ip和服务端的端口)
addrlen:服务端地址信息结构长度
返回值:
成功:0
失败:-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用:从已完成连接队列当中获取已经完成三次握手的连接
sockfd:套接字描述符
addr:客户端的地址信息结构(客户端的ip和客户端的端口)
addrlen:客户端地址信息结构的长度
返回值:
成功:新连接的套接字描述符,也是个文件描述符
失败:-1
①TCP服务端在通信之前创建套接字A,套接字当中有发送缓冲区和接收缓冲区,在绑定地址信息和监听后便可以接收新连接
②此时应该客户端发起连接,发起连接后与TCP服务端创建的套接字A产生了关联,即三次握手的数据是在套接字A中处理的
③调用accept后会在服务端中再创建一个套接字B,也就是新连接的套接字,随后客户端与套接字B进行数据的收发
④套接字A为监听套接字即“listen sock”,套接字B为“new sock”
⑤若此时再有一个客户端想建立连接,发起连接后同样是与套接字A进行三次握手建立连接,在建立连接后客户端会再创建一个
新套接字C与新的客户端进行数据收发
如果没有连接的时候,调用accept也会阻塞
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:套接字描述符
accept的返回值即新连接的套接字
buf:待要发送的数据
len:发送数据的长度
flags:
0:阻塞发送
MSG_OBB:发送带外数据
返回值:
大于0:返回发送的字节数量
-1:发送失败
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:套接字描述符
accept的返回值即新连接的套接字
buf:将接收的数据放到哪里去
len:buf的最大接收能力
flags:0 阻塞接收
返回值:
大于0:正常接收了多少字节数据
等于0:对端将连接关闭了
小于0:接收失败
如果连接没有数据,则调用recv会阻塞
int close(int fd);
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/socket.h>
4 #include <netinet/in.h>
5 #include <arpa/inet.h>
6
7 int main()
8 {
9 //建立套接字
10 int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
11 if(listen_sock < 0)
12 {
13 perror("listen_sock failed\n");
14 return 0;
15 }
16
17 //绑定
18 struct sockaddr_in addr;
19 addr.sin_family = AF_INET;
20 addr.sin_port = htons(12345);
21 addr.sin_addr.s_addr = inet_addr("10.0.8.14");//使用ifconfig邦定内网地址
22 int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
23 if(ret < 0)
24 {
25 perror("bind failed\n");
26 return 0;
27 }
28
29 //监听
30 ret = listen(listen_sock, 1);
31 if(ret < 0)
32 {
33 perror("listen failed\n");
34 return 0;
35 }
36
37 while(1)
38 {
39 sleep(1);
40 }
41 return 0;
42 }
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <sys/socket.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7
8 int main()
9 {
10 //创建套接字
11 int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
12 if(listen_sock < 0)
13 {
14 perror("listen_sock failed\n");
15 return 0;
16 }
17
18 //绑定
19 struct sockaddr_in addr;
20 addr.sin_family = AF_INET;
21 addr.sin_port = htons(12345);
22 addr.sin_addr.s_addr = inet_addr("10.0.8.14");//使用ifconfig邦定内网地址
23 int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
24 if(ret < 0)
25 {
26 perror("bind failed\n");
27 return 0;
28 }
29
30 //监听
31 ret = listen(listen_sock, 1);
32 if(ret < 0)
33 {
34 perror("listen failed\n");
35 return 0;
36 }
37
38 while(1)
39 {
40 //建立新连接
41 struct sockaddr_in peer_addr;
42 socklen_t peer_addrlen = sizeof(peer_addr);
43 int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
44 if(new_sock < 0)
45 {
46 perror("accept failed\n");
47 return 0;
48 }
49
50 //接收成功
51 printf("accept %s:%d, create new_sock %d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);
52
53
54 //先接收
55 char buf[1024] = {0};
56 ssize_t recv_size = recv(new_sock, buf, sizeof(buf) - 1, 0);
57 if(recv_size < 0)
58 {
59 perror("recv failed\n");
60 continue;
61 }
62 else if(recv_size == 0)//对端关闭
63 {
64 perror("peer shutdown\n");
65 close(new_sock);//对端关闭则关闭自己
66
67 close(listen_sock);
68 return 0;
69 }
70
71 //接收成功
72 printf("client %d say: %s\n", new_sock, buf);
73
74 //发送
75 memset(buf, '\0', sizeof(buf));//清空buf
76 sprintf(buf, "i am server, hello client %d\n", new_sock);
77
78 ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
79 if(send_size < 0)
80 {
81 perror("send failed\n");
82 continue;
83 }
84 }
85
86 close(listen_sock);
87 return 0;
88 }
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <sys/socket.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7
8 int main()
9 {
10 int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
11 if(sockfd < 0)
12 {
13 perror("sockfd failed\n");
14 return 0;
15 }
16
17 //连接
18 struct sockaddr_in dest_addr;
19 dest_addr.sin_family = AF_INET;
20 dest_addr.sin_port = htons(12345);
21 dest_addr.sin_addr.s_addr = inet_addr("159.75.12.23");
22 int ret = connect(sockfd, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
23 if(ret < 0)
24 {
25 perror("connect failed\n");
26 return 0;
27 }
28
29 while(1)
30 {
31 //发送
32 char buf[1024] = "i am tcp client 1";
33 ssize_t send_size = send(sockfd, buf, strlen(buf), 0);
34 if(send_size < 0)
35 {
36 perror("send failed\n");
37 continue;
38 }
39
40 //接收
41 memset(buf, '\0', sizeof(buf));
42 ssize_t recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
43 if(recv_size < 0)
44 {
45 perror("recv fail\n");
46 continue;
47 }
48 else if(recv_size == 0)
49 {
50 printf("server shutdown\n");
51 close(sockfd);
52 return 0;
53 }
54
55 printf("server say: %s\n", buf);
56 }
57
58 return 0;
59 }
客户端同单线程
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <sys/socket.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 #include <signal.h>
8 #include <sys/wait.h>
9
W> 10 void sigcallback(int signo)
11 {
12 wait(NULL);
13 }
14
15 int main()
16 {
17 signal(SIGCHLD, sigcallback);
18
19 //创建套接字
20 int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
21 if(listen_sock < 0)
22 {
23 perror("listen_sock failed\n");
24 return 0;
25 }
26
27 //绑定
28 struct sockaddr_in addr;
29 addr.sin_family = AF_INET;
30 addr.sin_port = htons(12345);
31 addr.sin_addr.s_addr = inet_addr("10.0.8.14");//使用ifconfig邦定内网地址
32 int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
33 if(ret < 0)
34 {
35 perror("bind failed\n");
36 return 0;
37 }
38
39 //监听
40 ret = listen(listen_sock, 1);
41 if(ret < 0)
42 {
43 perror("listen failed\n");
44 return 0;
45 }
46
47 while(1)
48 {
49 //建立新连接
50 struct sockaddr_in peer_addr;
51 socklen_t peer_addrlen = sizeof(peer_addr);
52 int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
53 if(new_sock < 0)
54 {
55 perror("accept failed\n");
56 return 0;
57 }
58
59 //接收成功
60 printf("accept %s:%d, create new_sock %d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);
61
62 pid_t pid = fork();//创建子进程
63 if(pid < 0)
64 {
65 perror("fork failed\n");
66 close(new_sock);
67 continue;
68 }
69 else if(pid == 0)
70 {
71 //child
72 //recv
73 close(listen_sock);//防止子进程受父进程中套接字的影响
74 while(1)
75 {
76 //先接收
77 char buf[1024] = {0};
78 ssize_t recv_size = recv(new_sock, buf, sizeof(buf) - 1, 0);
79 if(recv_size < 0)
80 {
81 perror("recv failed\n");
82 continue;
83 }
84 else if(recv_size == 0)//对端关闭
85 {
86 perror("peer shutdown\n");
87 close(new_sock);//对端关闭则关闭自己
88
89 close(listen_sock);
90 return 0;
91 }
92
93 //接收成功
94 printf("client %d say: %s\n", new_sock, buf);
95
96 //发送
97 memset(buf, '\0', sizeof(buf));//清空buf
98 sprintf(buf, "i am server, hello client %d\n", new_sock);
99
100 ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
101 if(send_size < 0)
102 {
103 perror("send failed\n");
104 continue;
105 }
106 }
107 }
108 else
109 {
110 //father
111 close(new_sock);
112 continue;
113 }
114 }
115
116 close(listen_sock);
117 return 0;
118 }
#pragma once//防止头文件重复包含
2
3 #include <stdio.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <pthread.h>
7 #include <sys/socket.h>
8 #include <netinet/in.h>
9 #include <arpa/inet.h>
10 #include <string>
11
12 using namespace std;
13
14 class Tcp_Svr
15 {
16 public:
17 Tcp_Svr()
18 {
19 sockfd_ = -1;
20 }
21
22 ~Tcp_Svr()
23 {
24 //close(sockfd_);
25 //可在此close或新建一个close接口
26 }
27
28 int CreatSocket()//创建套接字
29 {
30 //sockfd
31 sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
32 if(sockfd_ < 0)
33 {
34 perror("sockfd_ failed");
35 return -1;
36 }
37 return sockfd_;
38 }
39
40 int Bind(const string& ip = "0.0.0.0", uint16_t port = 12345)//绑定地址信息
41 {
42 //使用sockfd
43 struct sockaddr_in addr;
44 addr.sin_family = AF_INET;
45 addr.sin_port = htons(port);
46 addr.sin_addr.s_addr = inet_addr(ip.c_str());
47 int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
48 if(ret < 0)
49 {
50 perror("bind failed");
51 return -1;
52 }
53 return 0;
54 }
55
56 int Listen(int backlog = 5)//监听
57 {
58 //使用sockfd
59 int ret = listen(sockfd_, backlog);
60 if(ret < 0)
61 {
62 perror("listen failed");
63 return -1;
64 }
65 return 0;
66 }
67
68 int Accept(struct sockaddr_in* peer_addr)//建立连接
69 {
70 //使用sockfd
71 socklen_t peer_addrlen = sizeof(struct sockaddr_in);
72 //如果直接给peer_addr到sizeof中只会传递一个指针的大小,传递一个类型的大小才是正确的
73 int ret = accept(sockfd_, (struct sockaddr*)peer_addr, &peer_addrlen);
74 if(ret < 0)
75 {
76 peer_addr = NULL;
77 return -1;
78 }
79
80 return ret;
81 }
82
83 void Close()
84 {
85 close(sockfd_);
86 }
87
88 void SetSockFd(int sockfd)
89 {
90 sockfd_ = sockfd;
91 }
92
93 int Send(const string& data)//发送
94 {
95 ssize_t send_size = send(sockfd_, data.c_str(), data.size(), 0);
96 if(send_size < 0)
97 {
98 perror("send failed");
99 return -1;
100 }
101 return send_size;
102 }
103
104 int Recv(string* data)
105 {
106 data->clear();//防止有脏数据
107 char buf[1024] = {0};
108 ssize_t recv_size = recv(sockfd_, buf, sizeof(buf) - 1, 0);
109 if(recv_size < 0)
110 {
111 perror("recv failed");
112 return -1;
113 }
114 else if(recv_size == 0)
115 {
116 printf("peer shutdown\n");
117 return -100;
118 }
119
120 data->assign(buf, strlen(buf));
121 return recv_size;
122 }
123
124 private:
125 int sockfd_;
126 };
1 #include "tcp_thread.hpp"
2
3 #define CHECK_RET(v) if(v < 0){return -1;}
4
5 void* tcp_thread_start(void* arg)
6 {
7 pthread_detach(pthread_self());
8 Tcp_Svr* new_ts = (Tcp_Svr*)arg;
9
10 while(1)
11 {
12 //接收
13 string data = "";
14 int ret = new_ts->Recv(&data);
15 if(ret == -100)
16 {
17 new_ts->Close();
18 delete new_ts;//释放
19 pthread_exit(NULL);
20 }
21 else if(ret <= -1)
22 {
23 continue;
24 }
25 else if(ret > 0)//大于0代表接收到了内容
26 {
27 printf("client say:%s\n", data.c_str());
28 }
29
30 //发送
31 data.clear();
32
33 data.assign("i am server");
34 new_ts->Send(data);
35 }
36
37 return NULL;
38 }
39
40 int main()
41 {
42 Tcp_Svr ts;
43 CHECK_RET(ts.CreatSocket());
44 CHECK_RET(ts.Bind());
45 CHECK_RET(ts.Listen());
46
47 while(1)
48 {
49 struct sockaddr_in peer_addr;
50 int new_sockfd = ts.Accept(&peer_addr);
51 if(new_sockfd < 0)
52 {
53 continue;//接收失败继续接收
54 }
55
56 //代码执行到这里,说明接收了一个新的客户端
57 //1、创建线程(在线程内部让创建出来的线程自己把自己分离)
58 //2、给线程传递的参数怎么传递
59
60 Tcp_Svr* new_ts = new Tcp_Svr();
61 if(new_ts == NULL)
62 {
63 //关闭套接字
64 close(new_sockfd);
65 continue;
66 }
67
68 new_ts->SetSockFd(new_sockfd);
69
70 pthread_t tid;
71 int ret = pthread_create(&tid, NULL, tcp_thread_start, (void*)new_ts);
72 if(ret < 0)
73 {
74 perror("pthread_create failed");
75 //关闭新连接套接字,以触发客户端感知到服务端有误了
76 new_ts->Close();
77 delete new_ts;
78 continue;
79 }
80
81 //主线程再次while(1)循环,进行accept
82 }
83 return 0;
84 }