一、socket介绍
套接字(socket)是Linux下的一种进程间通信的机制。这个机制不仅可以在不同主机之间实现网络通信,也可以在同一台主机上的不同应用程序完成通信。socket通常使用客户端<----->服务端这种模式完成通信。多个客户端可以同时连接到同一个服务器,由服务器完成数据处理,再将结果返回给客户端。
socket是应用层与TCP/IP协议通信间的中间软件抽象层,它是一组接口。
二、socket编程
使用socket接口需要包含的头文件:
//Linux
#include <sys/types.h>
#include <sys/socket.h>
//Windows
#pragma comment(lib,"ws2_32.lib")
#include <sys/types.h>
#include <WinSock2.h>
1、创建一个socket-----socket()
int socket(int domain, int type, int protocol);
socket():用于创建一个网络通信端点(相当于打开了一道门,供数据流出和流入)。
- domain:用于指定一个通信域(通常选择AF_INET)
协议族名字 | 说明 |
---|---|
AF_UNIX,AF_LOCAL | Local communication [unix(7)] |
AF_INET | IPv4 Internet protocols [ip(7)] |
AF_INET6 | IPv6 Internet protocols [ipv6(77) |
AF_IPX | IPX - Novell protocols |
AF_NETLINK | Kernel user interface device [netlink(7)] |
。。。。。 | 。。。。。 |
- type:指定套接字的类型
类型名字 | 作用 |
---|---|
SOCK_STREAM | 提供有序的,可靠的,双向的,基于连接通道的字节流,能保证数据完整的传送给对方,用于TCP协议;可以支持带外数据传输机制 |
SOCK_DGRAM | 固定长度,无连接,不可靠的报文传递,用于UDP协议 |
SOCK_SEQPACKET | 固定长度,有序的,可靠的,面向连接的报文传递 |
SOCK_RAW | 表示原始套接字,它允许应用程序访问网络层的原始数据包,此套接字类型使用较少 |
SOCK_RDM | 提供不保证排序的可靠数据报层 |
SOCK_PACKET | 已经过时 |
- protocol:通常设置为0,表示为给定的通信域和套接字协议类型选择默认协议。
样例:
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
int socketfd = socket(AF_INET,SOCK_STREAM,0);
if (socketfd < 0)
{
cout << "failed to create socket" << endl;
return -1;
}
cout << "success to create socket" << endl;
return 0;
}
2、bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
此函数用来将一个IP地址或端口号与一个socket绑定在一起(即套接字和地址/端口 互相关联)。一般情况下,会将一个服务器的套接字绑定到一个众所周知的IP地址和端口号,即一个固定的服务器(客户端应用程序提前就直到的IP地址和端口号)。因为对于客户端与服务器通信,客户端需要直到目的地址的IP地址和端口号。
- sockfd:要绑定的套接字
- addr:一个指针,指向struct sockaddr
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
sa_data:通用的socket地址结构体,char类型数组,一共14个字节,包括IP地址、端口号等信息。(用户无法进行对其数组赋值)对开发用户不好。
一般我们使用struct sockaddr_in结构体(sockaddr_in和sockaddr是并列的结构体,两者的占用空间一样,所以指向sockaddr_in的指针也可以指向sockaddr结构体)。
struct sockaddr_in {
sa_family_t sin_family; /* 协议族 */
in_port_t sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP 地址 */
unsigned char sin_zero[8];
};
- addrlen:指定了addr所指向的结构体对于的字节长度
样例:
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
int socketfd = socket(AF_INET,SOCK_STREAM,0);
if (socketfd < 0)
{
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(111);
int ret = bind(socketfd, (const sockaddr*)&addr, sizeof(addr));
if (ret < 0)
{
cout << "bind failed" << endl;
return -1;
}
cout << "success bind" << endl;
return 0;
}
注意:
这里的htons和htonl作用是为了避免大小端的问题。
bind()函数并不是总需要调用的,如果用户进程并不需要与一个具体IP地址和端口号互联的话。
3、listen()
int listen(int sockfd, int backlog);
此函数只能在服务器进程中使用,让服务器进入监听状态,等待客户端的连接请求。
一般在bind()函数后,accept()函数前调用。
- sockfd:server套接字
- backlog:sockfd的等待连接队列能够达到的最大值。一个服务器会存在多个客户端同时连接服务器,这些请求连接会放入队列中让服务器按照先后顺序依次处理,而这个参数就是告诉内核来控制队列的上限。当客户端请求连接队列满时,会受到一个表示连接失败的错误,本次连接会直接丢弃掉不做任何处理。
4、accpet()
服务器调用此函数之后,就会获取客户端的连接请求并建立起连接。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5、connect()
此函数用于客户端中,客户端调用connect()将socket与服务器进行连接。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
TCP:发生在TCP连接的握手过程中,最终建立一个TCP连接。
UDP:只是在sockfd中记录服务器IP地址和端口号,并不会发送数据。
6、发送数据和接受数据
-
read()
读取指定字节大小的数据放入到指定的缓冲区中。 -
recv()
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
flags:通常设置为0
flags | 描述 |
---|---|
MSG_CMSG_CLOEXEC | 为UNIX域套接字上接受的文件描述符设置执行时关闭标志 |
MSG_DONTWAIT | 启动非阻塞操作 |
MSG_ERRQUEUE | 接受错误信息作为辅助数据 |
MSG_OOB | 如果协议支持,获取带外数据 |
MSG_PEEK | 返回数据包内容而不真正的取走数据包 |
MSG_TRUNC | 即使数据包被拦截,也返回数据包的长度 |
MSG_WAITALL | 等待直到所有的数据可用(只用于SOCK_STREAM) |
- write()
- send()
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- closesocket():关闭套接字
三、IP地址格式转换函数
通常的IP地址形式:xxx.xxx.xxx.xxx ,这是一种字符串的形式,而计算器需要处理的是二进制的数据,因此需要将IP地址由字符串转换成二进制。
头文件:** Ws2tcpip.h**
1、inet_aton、inet_addr、inet_ntoa
- inet_aton:将网络字节序转换为IPv4地址。
- inet_ntoa:将IPv4 地址转换为网络字节序。
- inet_addr:将点分十进制的 IPv4 地址字符串转换为网络字节序。
2、inet_pton、inet_ntop
inet_pton是Internet Protocol to Presentation Numeric的缩写
可以将点分文本的IP地址转换为二进制网络字节序”的IP地址,而且inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。
int inet_pton(int af, const char *src, void *dst);
- af:必须是AF_INET(ipv4)或者AF_INET6(ipv6)
- src:转化的地址
- dst:对应struct in_addr/struct in6_addr 结构体
inet_ntop是Internet Numeric To Presentation的缩写。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
样例:
#include <iostream>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
struct in_addr addr;
inet_pton(AF_INET,"192.168.0.1",&addr);
cout << addr.S_un.S_addr << endl;
char buf[40];
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
cout << buf << endl;
return 0;
}
四、一些其它函数
int setsockopt (int sockfd, int level, int optname, const void *optval, socklen_t optlen);
(1) int sockfd: 很简单,socket句柄
(2) int level: 选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次
(3) int optname: 需设置的选项
(4) const void *optval: 指针,指向存放选项值的缓冲区
(5) socklen_t optlen: optval缓冲区的长度
optname定义如下:
#define SO_DEBUG 1 -- 打开或关闭调试信息
#define SO_REUSEADDR 2 -- 打开或关闭地址复用功能
#define SO_TYPE 3 --
#define SO_ERROR 4
#define SO_DONTROUTE 5
#define SO_BROADCAST 6
#define SO_SNDBUF 7 -- 设置发送缓冲区的大小
#define SO_RCVBUF 8 -- 设置接收缓冲区的大小
#define SO_KEEPALIVE 9 -- 套接字保活
#define SO_OOBINLINE 10
#define SO_NO_CHECK 11
#define SO_PRIORITY 12 -- 设置在套接字发送的所有包的协议定义优先权
#define SO_LINGER 13
#define SO_BSDCOMPAT 14
#define SO_REUSEPORT 15
#define SO_PASSCRED 16
#define SO_PEERCRED 17
#define SO_RCVLOWAT 18
#define SO_SNDLOWAT 19
#define SO_RCVTIMEO 20 -- 设置接收超时时间
#define SO_SNDTIMEO 21 -- 设置发送超时时间
#define SO_ACCEPTCONN 30
#define SO_SNDBUFFORCE 32
#define SO_RCVBUFFORCE 33
#define SO_PROTOCOL 38
#define SO_DOMAIN 39
ntohs
ntohs 是一个函数,它用于将网络字节序(Network Byte Order)中的 16 位无符号整数(unsigned short)转换为主机字节序(Host Byte Order)。
send和sendto
int send(int sockfd, const void *buf, size_t len, int flags);
send 函数通常用于已连接的套接字上发送数据,它不需要指定目标地址,因为在调用 send 函数之前,套接字已经与远程主机建立了连接。这个函数通常用于 TCP 套接字。
int sendto(int s, const void *buf, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
sendto 函数通常用于在无连接的套接字上发送数据,它可以向指定的目标地址发送数据,并且可以指定一些其他参数,比如目标地址的长度等。这个函数通常用于 UDP 套接字。
总的来说,sendto 适用于 UDP 套接字,而 send 适用于 TCP 套接字。