Linux 网络套接字
网络套接字编程
# 1. Linux网络编程接口
# 1.1 套接字的文件描述符
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol);
1
2
3
domain
:设置通讯的协议。可选宏定义name:
Name Purpose Man Page AF_UNIX, AF_LOCAL 本地 unix(7) AF_INET IPv4 ip(7) AF_INET6 IPv6 ipv6(7) AF_IPX IPX AF_PACKET 低级数据包接口 packet(7)
type
:指定通信语义
Type description SOCK_STREAM 提供有序、可靠、双向、基于连接的字节流。可以支持带外数据传输机制(全双工,类似于管道) TCP
SOCK_DGRAM 支持数据报(固定最大长度的无连接、不可靠消息)。 UDP
SOCK_SEQPACKET 为固定最大长度的数据报提供有序、可靠、基于双向连接的数据传输路径;消费者需要在每次输入系统调用时读取整个数据包。 SOCK_RAW 提供原始网络协议访问。 SOCK_RDM 提供一个可靠的数据报层,该层不保证顺序。 SOCK_PACKET 过时(不再使用) SOCK_NONBLOCK 在新打开的文件描述上设置 O_NONBLOCK
标志。使用此标志可以节省对fcntl(2)
的额外调用,却能获得相同的结果。SOCK_CLOEXEC 在新文件描述符上设置close-on-exec( FD_CLOEXEC
)标志。参阅open(2)
protocol
:指定要与套接字一起使用的特定协议,通常设为0;返回值:成功返回文件描述符
fd
;失败返回-1.
# 1.2 IP地址和端口与进程(套接字)的绑定
使用
sockaddr_in
初始化绑定的地址和端口号,端口号16bit,IPV4通常是32bit的数据。#include <netinet/in.h> #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { __be32 s_addr; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
1
2
3
sockfd
:需要绑定的文件描述符
addr
:给套接字分配的地址。
addrlen
:由addr
指向的地址结构体字节长度。返回值:成功返回0;失败返回-1.
# 1.3 发送和接收的接口函数
发送和接收本质是读取和写入发送和接收缓冲区,使用read
和write
也能达到类似的效果。
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); //UDP ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
1
2
3
4
5
6
7
8
9
recv
通常仅在连接的套接字上使用;recvfrom
和recvmsg
用于从套接字接收消息和数据,无论其是否面向连接。所有函数返回:成功返回消息的长度,若提供的buf
装不下, 超出的字节可能会忽略。若套接字消息为空,则接受阻塞等待(若为非阻塞状态,返回为-1)
- recvfrom
src_addr: (struct sockaddr *)sockaddr_in
flags: 0 为阻塞式读取。
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); //UDP ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
1
2
3
4
5
6
7
8
9
send
调用只能在套接字处于连接状态时使用(以便知道预期的收件人)。send
和write
之间的唯一区别是是否存在标志。对于0
标志参数,send
相当于write
。send(sockfd, buf, len, flags);
=sendto(sockfd, buf, len, flags, NULL, 0);
对于
send
和sendto
,消息位于buf
中,长度为len
。对于sendmsg
,消息由数组msg
的元素msg.msg_iov
指向。调用还允许发送辅助数据(也称为控制信息)。如果消息太长,无法通过基础协议进行原子传递,则返回错误EMSGSIZE,并且不会传输消息。
# 1.4 数据格式的转换
由于从网络到主机的数据存在大端小端、字符字节长度等数据格式的问题,需要将数据转化为特定的格式。这个过程可以更具当前的系统自己实现,当然也可以调用系统提供的函数接口。
网络上的数据传输一般是大端模式(即数据低字节存在内存高地址),数据的发送从内存的低地址到高地址,这样在发送的时候会先发数据的高位字节(报头),这样接受端可以快速判断数据的正负和大小。
- 字符类型的转换
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //将无符号整数hostlong从主机字节顺序转换为网络字节顺序
uint16_t htons(uint16_t hostshort); //将短整数hostshort从主机字节顺序转换为网络字节顺序。
uint32_t ntohl(uint32_t netlong); //无符号整数hostlong 网络——>主机
uint16_t ntohs(uint16_t netshort); //短整数hostshort 网络——>主机
2
3
4
5
6
7
8
9
- 主机字节序和网络字节序的转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//*****
int inet_aton(const char *cp, struct in_addr *inp);
//将Internet主机地址cp从IPv4点分十进制形式转换为二进制形式(按网络字节顺序)并存储它位于inp指向的结构中。如果地址有效,inet_aton()返回非零;如果地址无效,则返回零。cp 的地址具有以下形式之一:a.b.c.d、 a.b.c、 a.b、 a
//*****
in_addr_t inet_addr(const char *cp);
//函数将Internet主机地址cp从IPv4点分十进制形式转换为网络字节顺序的二进制数据(主机到网络)
in_addr_t inet_network(const char *cp);
//将IPv4点分十进制形式的字符串cp, 转换为适合用作Internet网络地址的主机字节序。成功后,将返回转换后的地址。如果输入无效,则返回-1。
char *inet_ntoa(struct in_addr in); //线程不安全 推荐inet_ntop,提供缓冲区保存结果、
//将以网络字节顺序形式的Internet主机地址,转换为IPv4点分十进制表示法的字符串。(网络到主机)
in_addr_t inet_lnaof(struct in_addr in);
//返回Internet地址in中的本地网络地址部分。返回的值按主机字节顺序排列。
in_addr_t inet_netof(struct in_addr in);
//返回Internet地址in中的网络号部分。返回的值按主机字节顺序排列。
struct in_addr inet_makeaddr(int net, int host);
//与inet_lnaof和inet_netof相反。将网络编号net与本地主机地址host组合在一起按主机字节顺序创建的网络主机地址,以网络字节序返回
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
# 1.5 tcp相关函数接口
由于tcp在数据传输前需要建立连接,对于服务器端需要listen监听和accept接受请求,对于客户端需要connect发送连接请求。
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
1
2
3
4
backlog
参数定义sockfd
的挂起连接队列可能增长到的最大长度。如果连接请求队列已满时,客户端的请求会收到一个错误。成功返回0;失败返回-1.
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
1
2
3
4成功返回一个文件描述符,失败返回-1.
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
1
2
3
4
connect
系统调用将文件描述符sockfd
引用的套接字连接到addr
指定的地址。如果
sockfd
的类型为SOCK_DGRAM
,那么addr
是默认情况下数据报发送到的地址,也是数据报收到的唯一地址。如果套接字的类型为SOCK_STREAM 或 SOCK_SEQPACKET,则此调用将尝试与addr
指向的地址建立链接。返回值:链接成功返回0;失败返回-1.
# 2. 两个例子
# 2.1 UDP
报文不可被分割,数据必须完整读取——面向数据报
UDP没有发送缓冲区,sendto会直接将数据交给内核;UDP具有接收缓冲区,但无法保证报文的顺序,缓冲区满会丢弃新到的数据。
应用:NFS网络文件系统、TFTP简单文件传输协议、DHCP动态主机配置协议、BOOTP启动协议、DNS动态域名解析
- udpserver.cpp
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>
class udpserver
{
public:
udpserver(const int& _port = 6666)
{
_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // IPV4 UDP
if(_sockfd == -1 )
{
std::cout << "socket create faild" << std::endl;
exit(-1);
}
_local.sin_addr.s_addr = htonl(INADDR_ANY); //开放所有的ip,INADDR_ANY宏定义为0
_local.sin_family = AF_INET;
_local.sin_port = htons(_port);
memset(&_local.sin_zero, 0, sizeof(_local.sin_zero));
if(bind(_sockfd, (struct sockaddr*)&_local, sizeof(_local)) == -1)
{
std::cout << "socket bind faild" << std::endl;
exit(-2);
}
}
void StartServer()
{
char buf[1024] = {0};
while(1)
{
struct sockaddr_in RemoteIp;
socklen_t len = sizeof(RemoteIp);
std::cout << "receive from client# ";
fflush(stdout);
ssize_t size = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&RemoteIp, &len);
if(size > 0)
{
buf[size] = '\0';
int port = ntohs(RemoteIp.sin_port);
std::string addr = inet_ntoa(RemoteIp.sin_addr);
std::cout << addr << ":" << port << " Says-> " << buf << std::endl;
}
}
}
~udpserver()
{
close(_sockfd);
}
private:
short _port;
int _sockfd;
struct sockaddr_in _local;
};
int main()
{
udpserver* udpS = new udpserver;
udpS->StartServer();
return 0;
}
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
- udpclient.cpp
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>
class udpclient
{
public:
udpclient(const std::string &serverIP = "127.0.0.1", const int &port = 6666)
:_serverIP(serverIP)
,_serverPort(port)
{
_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // IPV4 UDP
if(_sockfd == -1 )
{
std::cout << "socket create faild" << std::endl;
exit(-1);
}
}
void StartClient()
{
while(1)
{
std::string buf;
std::cout << "Message Send$ ";
fflush(stdout);
std::cin >> buf;
struct sockaddr_in Send2Remote;
Send2Remote.sin_addr.s_addr = inet_addr(_serverIP.c_str());
Send2Remote.sin_family = AF_INET;
Send2Remote.sin_port = htons(_serverPort);
memset(&Send2Remote.sin_zero, 0, sizeof(Send2Remote.sin_zero));
socklen_t len = sizeof(Send2Remote);
sendto(_sockfd, buf.c_str(), buf.size(), 0, (struct sockaddr*)&Send2Remote, len);
}
}
~udpclient()
{
close(_sockfd);
}
private:
std::string _serverIP;
int _serverPort;
int _sockfd;
};
int main()
{
udpclient* udpC = new udpclient;
udpC->StartClient();
return 0;
}
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
# 2.2 TCP
- tcpserver.cpp
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
class tcpserver
{
public:
tcpserver(const int& _port = 1666)
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0); // IPV4 UDP
if(_sockfd == -1 )
{
std::cout << "socket create faild" << std::endl;
exit(-1);
}
_local.sin_addr.s_addr = htonl(INADDR_ANY); //开放所有的ip,INADDR_ANY宏定义为0
_local.sin_family = AF_INET;
_local.sin_port = htons(_port);
memset(&_local.sin_zero, 0, sizeof(_local.sin_zero));
if(bind(_sockfd, (struct sockaddr*)&_local, sizeof(_local)) == -1)
{
std::cout << "socket bind faild" << std::endl;
exit(-2);
}
//建立连接
if(listen(_sockfd, 10) < 0)
{
std::cout << "listen failed" << std::endl;
exit(-3);
}
}
void MyService(const std::string &ip, const int &port)
{
char buf[1024] = {0};
while(1)
{
ssize_t size = read(_Remotefd, buf, sizeof(buf) - 1);
if(size > 0) //读到了size个字符
{
buf[size] = '\0';
std::cout << "received from client(" << ip << ":" << port << ")# " << buf << std::endl;
write(_Remotefd, buf, size);
}
else if(size == 0) //读到了文件的结尾,对端关闭了连接
{
std::cout << "(" << ip << ":" << port << ")# closed" << std::endl;
break;
}
else //发生了错误
{
std::cout << "read from client err" << std::endl;
break;
}
// std::cout << "size = " << size << std::endl;
}
}
void StartServer()
{
char buf[1024] = {0};
while(1)
{
struct sockaddr_in RemoteIp;
socklen_t len = sizeof(RemoteIp);
std::cout << "receive from client# ";
fflush(stdout);
//两个文件描述符,_sockfd:用来获取远程端口信息;_Remotefd: 给远端提供服务。
_Remotefd = accept(_sockfd, (struct sockaddr*)&RemoteIp, &len);
if(_Remotefd == -1)
{
std::cout << "accept failed" << std::endl;
continue;
}
std::cout << "sock file descriptor:" << _Remotefd << std::endl;
std::string ip = inet_ntoa(RemoteIp.sin_addr);
int port = ntohs(RemoteIp.sin_port);
// 线程方式接受客户端
// pthread_t tid;
// pthread_create(&tid, nullptr,ThreadService, (void*)&_Remotefd );
// 进程方式接受客户端
// pid_t pid = fork();
// if(pid == 0)
// {
// close(_sockfd);
// if(fork() > 0)
// {
// exit(0);
//退出后,产生孤儿进程,将子进程交给操作系统管理;另外对于它的父进程来说,相当于子进程退出,产生了僵尸进程
// }
// MyService(ip, port); //父进程执行服务
// exit(0);
// }
// close(_Remotefd); // service完成后关闭,防止文件描述符的占用
// waitpid(pid, nullptr, 0);
}
}
~tcpserver()
{
close(_sockfd);
}
private:
short _port;
int _sockfd;
int _Remotefd;
struct sockaddr_in _local;
};
int main()
{
tcpserver* udpS = new tcpserver;
udpS->StartServer();
return 0;
}
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
- tcpclient.cpp
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>
class tcpclient
{
public:
tcpclient(const std::string &serverIP = "1.116.204.69", const int &port = 1666)
:_serverIP(serverIP)
,_serverPort(port)
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(_sockfd == -1 )
{
std::cout << "socket create faild" << std::endl;
exit(-1);
}
}
void Service()
{
std::string msg;
char buf[1024] = {0};
while(1)
{
std::cout << "Send message:" ;
fflush(stdout);
std::cin >> msg;
write( _sockfd, msg.c_str(), msg.size() );
ssize_t size = read(_sockfd, buf, sizeof(buf) - 1);
if(size > 0)
{
buf[size] = '\0';
std::cout << "received from server# " << buf << std::endl;
}
else
{
std::cout << "server closed" << buf << std::endl;
break;
}
}
}
void StartClient()
{
_RemoteSocket.sin_addr.s_addr = inet_addr(_serverIP.c_str());
_RemoteSocket.sin_family = AF_INET;
_RemoteSocket.sin_port = htons(_serverPort);
memset(&_RemoteSocket.sin_zero, 0, sizeof(_RemoteSocket.sin_zero));
if(connect(_sockfd, (struct sockaddr*)&_RemoteSocket , sizeof(_RemoteSocket)) == -1)
{
std::cout << "connect failed" << std::endl;
exit(-2);
}
else
{
// std::cout << "hello wordl" << std::endl;
Service();
}
}
~tcpclient()
{
close(_sockfd);
}
private:
std::string _serverIP;
int _serverPort;
int _sockfd; //用来建立连接
struct sockaddr_in _RemoteSocket;
};
int main()
{
tcpclient* tcl = new tcpclient;
tcl->StartClient();
return 0;
}
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