TCP介绍
TCP服务端
实现流程
socket函数
int socket(int domain, int type, int protocol);
:创建一个socket文件描述符fd
domain
:输入参数,协议域、地址域或协议族
。type
:输入参数,socket类型;protocol
:输入参数,指定使用的协议;- 返回值 :成功返回一个非负整数的fd,失败则返回-1,并设置errno
- 头文件 :
<sys/socket.h>
常用 domain:AF_INET
、AF_INET6
、AF_LOCAL
(或称AF_UNIX
)、AF_ROUTE
常用 type:SOCK_STREAM
、SOCK_DGRAM
、SOCK_RAW
、SOCK_PACKET
、SOCK_SEQPACKET
等等
常用 protocol:IPPROTO_TCP
、IPPTOTO_UDP
、IPPROTO_SCTP
、IPPROTO_TIPC
等等。如果该参数为0,则让系统自动选择合适的协议,一般我们也这么操作
bind函数
分配了一个Socket描述符后,服务端的第二步就是使用在这个描述符用Bind绑定。
Bind()系统调用的主要用处:
服务器向系统注册它的众所周知的地址。面向连接和无连接的服务器在接受客户的请求之前都必须做这一步。
客户可为自己注册一个特定的地址,以便服务器可以用这个有效的地址送回响应。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
:绑定一个固定的socket地址
sockfd
:输入参数,socket文件描述符,即socket()的返回值addr
:输入参数,指向sock地址的指针addrlen
:输入参数,sock地址的长度- 返回值:成功返回0,失败返回-1,并设置errno
listen函数
int listen(int sockfd, int backlog);
:监听socket套接字是否有客户端连接它
sockfd
:输入参数,被监听的套接字backlog
:输入参数,最大等待队列数量,宏SOMAXCONN
为系统设定的最大值,可通过/etc/sysctl.conf
修改,是在Server程序调用accept函数之前最大允许进入的连接请求数,多余的连接请求将被拒绝- 返回值:成功返回0,失败返回-1,并设置errno
accept函数
在listen监听到有新客户端时,就可以用accept函数响应客户的连接请求,建立与客户端的连接。产生一个新的socket描述符来描述该连接,这个连接用来与发起该连接请求的客户交换数据。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
:接受socket连接请求
sockfd
:输入参数,被监听的套接字addr
:输出参数,返回客户端的地址,可为NULLaddrlen
:输入参数,返回addr客户端的地址的长度,可为NULL- 返回值:成功返回已连接的新套接字描述符connfd,失败返回-1,并设置errno
recv函数
int recv(int sockfd, void *buf, int len, int flags);
:从socket接收数据
sockfd
:输入参数,从这个socket接收数据buf
:输出参数,用来保存接收到的数据len
:输入参数,指定buf的长度,表示最多接收这么多个字节的数据flags
:输入参数,flags,指定对应的选项,一般置为0,但可以选择下列设置:- MSG_DONTROUTE:表示不使用指定路由,对send、sendto有效
- MSG_PEEK:对recv, recvfrom有效,表示读出网络数据后不清除已读的数据
- MSG_OOB:对发送接收都有效,表示发送或接受加急数据
- 返回值:成功返回接收到的数据大小,返回0表示对方不再发送数据(可以理解为关闭了连接),出错返回-1,并设置errno
send函数
int send(int sockfd, void *buf, int len, int flags);
:向socket发送数据
sockfd
:输入参数,向这个socket发送数据buf
:输入参数,发送buf指向的数据len
:输入参数,指定buf的长度,指定要发送的数据大小,为要发送的数据的实际字节数+1flags
:输入参数,flags,指定对应的选项,一般置为0,但可以选择下列设置:- MSG_DONTROUTE:表示不使用指定路由,对send、sendto有效
- MSG_PEEK:对recv, recvfrom有效,表示读出网络数据后不清除已读的数据
- MSG_OOB:对发送接收都有效,表示发送或接受加急数据
- 返回值:成功返回已发送的数据大小,失败则返回-1,并设置errno
recvfrom函数
int recvfrom(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t *addrlen);
:从udp socket接收数据
sockfd
:输入参数,从该socket接收数据buf
:输出参数,将接收的数据存放在buf上len
:输入参数,指定buf的长度flags
:输入参数,flags,指定对应的选项,一般置为0addr
:输出参数,保存该数据的发送方地址addrlen
:输入参数,指定发送方地址的长度- 返回值:成功返回接收到的数据大小,失败则返回-1,并设置errno
sendto函数
int sendto(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t addrlen);
:向udp socket发送数据
sockfd
:输入参数,向该socket发送数据buf
:输入参数,发送buf指向的数据len
:输入参数,指定buf的长度flags
:输入参数,flags,指定对应的选项,一般置为0addr
:输入参数,指定接收方的地址addrlen
:输入参数,指定接收方的地址的长度- 返回值:成功返回发送的数据大小,失败则返回-1,并设置errno
最后一个参数flags
:
MSG_WAITALL
:用于recv,尽可能等待所有数据MSG_DONTWAIT
:用于recv、send,仅本次操作不阻塞MSG_DONTROUTE
:用于send,绕过路由表查找MSG_OOB
:用于recv、send,发送或接收带外数据MSG_PEEK
:用于recv,窥看外来数据,不将其从socket缓冲区中删除
shutdown函数
int shutdown(int sockfd, int howto);
:关闭tcp连接
sockfd
:输入参数,即将关闭连接的套接字howto
:输入参数,定义如何关闭:SHUT_RD
值为0,关闭读、SHUT_WR
值为1,关闭写、SHUT_RDWR
值为2,关闭读写- 返回值:成功返回0,失败返回-1,并设置errno
close函数
int close(int fd);
:关闭socket
fd
:输入参数,要关闭的文件描述符fd- 返回值:成功返回0,失败返回-1,并设置errno
shutdown和close区别
shutdown()
函数用于tcp连接的socket套接字,对udp无效
用于更精细的控制流的读和写,可以只关闭读,也可以只关闭写,或者读写都关闭,但是关闭后,该sockfd依旧是有效的,仍需调用close进行关闭
而close()
函数用于关闭对应的fd文件描述符,socket也是特殊的文件,注意,close只是将对应的sockfd的引用计数减1,当引用计数减到0时,系统才关闭该sockfd
还有一点:在多进程程序中,使用shutdown会导致共享进程的连接也被关闭,读写出现错误,而close不会影响共享进程的socket
程序
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
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define LISTEN_PORT 4160
#define BUF_SIZE 128
int main() {
int listenfd;
printf("Socket Server start ...\n");
// 1. Create socket
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket create failed");
return -1;
}
// 2. Bind socket
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 32位无符号整数从主机字节顺序转成网络字节顺序
server_addr.sin_port = htons(LISTEN_PORT); // 16位无符号短整数(端口号)从主机字节顺序转成网络字节顺序
int error;
// error = bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){
perror("bind_listenfd error");
return -1;
}
// 3. Listen
if (listen(listenfd, SOMAXCONN) < 0) {
perror("listen_listenfd error");
return -1;
}
// 4. Accept
int connfd;
struct sockaddr_in peer_addr;
socklen_t peer_addr_size = sizeof(peer_addr);
char buf[BUF_SIZE];
int nbuf;
while (1) {
if ((connfd = accept(listenfd, (struct sockaddr *)&peer_addr, &peer_addr_size)) < 0) {
perror("accept_listenfd error");
continue;
}
nbuf = recv(connfd, buf, BUF_SIZE, 0);
buf[nbuf] = '\0';
if (!strcmp(buf, "exit")) {
printf("exit_server\n");
close(connfd);
break;
}
printf("new conn(%s:%d); msg: %s\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), buf);
send(connfd, buf, nbuf, 0);
close(connfd);
}
close(listenfd);
return 0;
}
TCP客户端
connect函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
:连接socket套接字
sockfd
:输入参数,客户端的套接字addr
:输入参数,服务器的地址addrlen
:输入参数,服务器的地址的长度- 返回值:成功返回0,失败返回-1,并设置errno
程序
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
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define SERV_PORT 4160
#define BUF_SIZE 128
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "usage: %s <MSG>\n", argv[0]);
return -1;
}
// 1. create socket
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("create_sockfd error");
return -1;
}
// 2. connect to server
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if (inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr) <= 0) {
perror("convert_servaddr error");
return -1;
}
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("connect_sockfd error");
return -1;
}
// 3. send message to server
char buf[BUF_SIZE];
int nbuf = strlen(argv[1]);
send(sockfd, argv[1], nbuf, 0);
nbuf = recv(sockfd, buf, BUF_SIZE, 0);
buf[nbuf] = '\0';
printf("echo msg: %s\n", buf);
close(sockfd);
return 0;
}