套接字
1. Linux网络编程接口
1.1 套接字的文件描述符
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol);
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; };
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
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);
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);
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 网络——>主机
- 主机字节序和网络字节序的转换
#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组合在一起按主机字节顺序创建的网络主机地址,以网络字节序返回
1.5 tcp相关函数接口
由于tcp在数据传输前需要建立连接,对于服务器端需要listen监听和accept接受请求,对于客户端需要connect发送连接请求。
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
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.
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect
系统调用将文件描述符sockfd
引用的套接字连接到addr
指定的地址。如果
sockfd
的类型为SOCK_DGRAM
,那么addr
是默认情况下数据报发送到的地址,也是数据报收到的唯一地址。如果套接字的类型为SOCK_STREAM 或 SOCK_SEQPACKET,则此调用将尝试与addr
指向的地址建立链接。返回值:链接成功返回0;失败返回-1.
2. 两个例子
2.1 UDP
- 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;
}
- 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.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;
}
- 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:
//填入自己服务器的ip地址
tcpclient(const std::string &serverIP = "xxx.xxx.xxx.xxx", 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;
}