目录
? socket(套接字)是网络编程编程的一种技巧。通过socket不仅可以实现跨进程通信,还可以实现跨主机的网络通信。使用这种技术,就可以实现全国各地的通讯。例如:深圳的一台电脑接收来自北京一台电脑发来的信息。 ? 本篇不涉及太底层的网络原理,仅说明socket的基本使用方法。主要参考《Linux网络编程》。本篇源码获取方式见文底小字。
? socket是通过标准的UNIX文件描述符和其他的程序通讯的一个方法。其可以实现同一主机不同进程间的通信;也可以实现不同主机间的通信。
? 套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)和原始套接字。
流式套接字(SOCK_STREAM) ? 流式的套接字可以提供可靠的、面向连接的通讯流。如果你通过流式套接字发送了顺序的数据:"1"、"2"。那么数据到达远程时候的顺序也是"1"、"2"。
面向连接的Socket工作流程
数据报套接字(SOCK_DGRAM) ? 数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。
原始套接字(SOCK_RAM) ? 原始套接字主要用于一些协议的开发,可以进行比较底层的操作。它功能强大,但是没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字。
connect()
。struct sockaddr {
unsigned short sa_family; /* address族, AF_xxx */
char sa_data[14]; /* 14 bytes的协议地址 */
};
sa_family
为指定协议族,通常使用到的有AF_INET、AF_UNIX等。
sa_data
为不同协议族通信时必要的数据。例如,sa_family
为AF_INET时,sa_data
要传IP地址和端口号。
sockaddr_in
是用于存储AF_INET的套接字地址,其中in就代表Inet。
? 介绍sockaddr
时,说到在使用AF_INET需要传IP和端口号,但并不知道要将IP和端口号填到sockaddr
的哪个地方。于是,设计了sockaddr_in
,定义出地址和端口号成员。在使用时只需要填充sockaddr_in
,传参时强转为sockaddr
即可(两个结构体大小一致)。struct sockaddr_in {
short int sin_family; /* Internet地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小) */
};
in_addr
为sockaddr_in
成员,用于存储4个字节的IP地址。需要注意的是,此值填写时需要按照网络字节来填充,可以通过一些转换函数完成。struct in_addr {
unsigned long s_addr;
};
sockaddr_un
是用于存储AF_UNIX的套接字地址,推测un就代表UNIX(没有求证)。
? 和上述类似,当使用AF_UNIX时,需要填充AF_UNIX的地址结构体sockaddr_un
,然后传参时强转为sockaddr
。struct sockaddr_un
{
uint8_t sun_len;
sa_family_t sun_family; /* AF_LOCAL */
char sun_path[104]; /* null-terminated pathname */
};
网络字节序转换 ? 上文描述填写IP和端口时要注意网络字节序,否则可能连接不到指定的设备。系统提供了如下几种函数方便转换:
IP地址转换
注:inet_ntoa()
返回一个字符指针,它指向一个定义在函数 inet_ntoa() 中的 static 类型字符串。所以每次调用 inet_ntoa(),都会改变最后一次调用 inet_ntoa() 函数时得到的结果。
? Linux同时支持面向连接和不连接类型的套接字。在面向连接的通讯中服务器和客户机在交换数据之前先要建立一个连接;在不连接通讯中数据被作为信息的一部分被交换。 ? 无论那一种方式,服务器总是最先启动,把自己绑定(Banding)在一个套接字上,然后侦听信息。
socket
主要使用到如下函数:
? TCP Socket可以理解为Inet使用流式套接字,为保证通讯稳定而采用TCP协议。其优点在于可靠、稳定。
TCP Socket 服务端
void DealClientMsgThread(int fd)
{
while(1)
{
char buf[1024] = {0};
int ret = read(fd, buf, sizeof(buf));
if (ret > 0) {
TSVR_LOG("# RECEIVE: %s.\n", buf);
// response dstAddr msg
char ack[20] = {0};
snprintf(ack, sizeof(ack), "Ack [%ld]", strlen(buf));
write(fd, ack, strlen(ack));
} else {
break;
}
}
TSVR_LOG("Disconnect.\n");
}
int main(int argc, char *argv[])
{
int i = 0;
std::thread threads[MAX_NUM_THREAD];
if (argc != 2)
{
TSVR_LOG("usage ./tcp_server 8080.\n");
return -1;
}
string port = argv[1];
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd == -1)
{
TSVR_LOGE("socket failed. (%s)\n", strerror(errno));
return -1;
}
int op = 1;
if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
TSVR_LOGE("setsockopt failed. (%s)\n", strerror(errno));
goto exit;
}
struct sockaddr_in myAddr;
bzero(&myAddr, sizeof(myAddr));
myAddr.sin_family = AF_INET;
myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
myAddr.sin_port = htons(atoi(port.c_str()));
if (bind(sockFd, (struct sockaddr *)&myAddr, sizeof(myAddr)) == -1) {
TSVR_LOGE("bind failed. (%s)", strerror(errno));
goto exit;
}
if (listen(sockFd, MAX_SIZE_BACKLOG) == -1) {
TSVR_LOGE("listen failed. (%s)\n", strerror(errno));
goto exit;
}
while(1)
{
// accept
struct sockaddr_in dstAddr;
socklen_t addrSize = (socklen_t)sizeof(dstAddr);
int conFd = accept(sockFd, (struct sockaddr *)&dstAddr, &addrSize);
if (conFd < 0) {
TSVR_LOGE("accept failed. (%s)\n", strerror(errno));
continue;
}
if (i < MAX_NUM_THREAD)
{
threads[i] = std::thread(DealClientMsgThread, conFd);
threads[i].detach();
i++;
} else {
TSVR_LOG("The number of threads reaches max(%d).\n", MAX_NUM_THREAD);
}
}
exit:
close(sockFd);
return 0;
}
TCP Socket 客户端
int main(int argc, char *argv[])
{
int op = 1024;
if (argc != 3)
{
TCLT_LOG("usage ./tcp_client <ip> <port>.\n");
return -1;
}
string ipAddr = argv[1];
string port = argv[2];
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd == -1)
{
TCLT_LOGE("socket failed. (%s)\n", strerror(errno));
return -1;
}
if (setsockopt(sockFd, SOL_SOCKET, SO_RCVBUF, &op, sizeof(op)) < 0) {
TCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
close(sockFd);
return -1;
}
if (setsockopt(sockFd, SOL_SOCKET, SO_SNDBUF, &op, sizeof(op)) < 0) {
TCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
close(sockFd);
return -1;
}
struct sockaddr_in dstAddr;
dstAddr.sin_family = AF_INET;
dstAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str());
dstAddr.sin_port = htons(atoi(port.c_str()));
// Linux TCP repeat connect directly return EISCONN.
if ( connect(sockFd, (struct sockaddr*)&dstAddr, sizeof(struct sockaddr_in)) < 0
&& errno != EISCONN)
{
TCLT_LOGE("connect %s:%d failed. (%s)\n", ipAddr.c_str(), atoi(port.c_str()), strerror(errno));
close(sockFd);
return -1;
}
thread rTh([&]() {
char recvBuf[1024] = {0};
TCLT_LOG("Start write thread.\n");
while(1)
{
int ret = read(sockFd, recvBuf, sizeof(recvBuf));
if (ret > 0) {
TCLT_LOG("# RECEIVE: %s.\n", recvBuf);
} else {
break;
}
}
});
thread wTh([&]() {
TCLT_LOG("Start read thread.\n");
while(1)
{
char sndBuf[] = "Hello World";
int ret = write(sockFd, sndBuf, strlen(sndBuf));
if (ret > 0)
{
TCLT_LOG("# SEND > %s.\n", sndBuf);
} else {
break;
}
sleep(2);
}
});
rTh.join();
wTh.join();
return 0;
}
代码是很简单的TCP通讯处理,为方便理解,不做过多封装。
? UDP Socket可以理解为Inet使用数据报套接字,为了快速通讯,客户端与服务端约定采用的UDP的套接字通讯。
UDP Socket 服务端
int main(int argc, char *argv[])
{
struct sockaddr_in dstAddr;
socklen_t addrSize = sizeof(struct sockaddr_in);
if (argc != 2)
{
USVR_LOG("usage ./udp_server 8080.\n");
return -1;
}
string port = argv[1];
int sockFd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockFd == -1)
{
USVR_LOGE("socket failed. (%s)\n", strerror(errno));
return -1;
}
int op = 1;
if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
USVR_LOGE("setsockopt failed. (%s)\n", strerror(errno));
goto exit;
}
struct sockaddr_in myAddr;
bzero(&myAddr, sizeof(struct sockaddr_in));
myAddr.sin_family = AF_INET;
myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
myAddr.sin_port = htons(atoi(port.c_str()));
if (bind(sockFd, (struct sockaddr *)&myAddr, sizeof(struct sockaddr_in)) < 0) {
USVR_LOGE("bind failed. (%s)\n", strerror(errno));
goto exit;
}
while(1)
{
char buf[1024] = {0};
int ret = recvfrom(sockFd, buf, sizeof(buf), 0,
(struct sockaddr *)&dstAddr, &addrSize);
if (ret > 0) {
char ack[20] = {0};
snprintf(ack, sizeof(ack), "Ack [%ld]", strlen(buf));
USVR_LOG("# RECEIVE: %s.\n", buf);
sendto(sockFd, ack, strlen(ack), 0,
(struct sockaddr *)&dstAddr, addrSize);
} else {
USVR_LOG("recvfrom failed. (%s)\n", strerror(errno));
break;
}
}
exit:
close(sockFd);
return 0;
}
UDP Socket 客户端
int main(int argc, char *argv[])
{
if (argc != 3)
{
UCLT_LOG("usage ./udp_client <ip> <port>.\n");
return -1;
}
string ipAddr = argv[1];
string port = argv[2];
int sockFd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockFd == -1)
{
UCLT_LOGE("socket failed. (%s)\n", strerror(errno));
return -1;
}
int op = 1024;
if (setsockopt(sockFd, SOL_SOCKET, SO_RCVBUF, &op, sizeof(op)) < 0) {
UCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
close(sockFd);
return -1;
}
struct sockaddr_in dstAddr;
bzero(&dstAddr, sizeof(struct sockaddr_in));
dstAddr.sin_family = AF_INET;
dstAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str());
dstAddr.sin_port = htons(atoi(port.c_str()));
thread wTh([&]() {
UCLT_LOG("Start write thread.\n");
while(1)
{
char sndBuf[] = "Hello World";
int ret = sendto(sockFd, sndBuf, strlen(sndBuf), 0,
(struct sockaddr *)&dstAddr, sizeof(struct sockaddr_in));
if (ret > 0) {
UCLT_LOG("# SEND: %s.\n", sndBuf);
} else {
break;
}
sleep(2);
}
});
thread rTh([&]() {
struct sockaddr_in dstAddr;
UCLT_LOG("Start read thread.\n");
while(1)
{
char recvBuf[1024] = {0};
socklen_t addrSize = sizeof(struct sockaddr_in);
bzero(&dstAddr, addrSize);
int ret = recvfrom(sockFd, recvBuf, sizeof(recvBuf), 0,
(struct sockaddr *)&dstAddr, &addrSize);
if (ret > 0)
{
UCLT_LOG("# RECEIVE: %s.\n", recvBuf);
} else {
break;
}
}
});
wTh.join();
rTh.join();
return 0;
}
用心感悟,认真记录,写好每一篇文章,分享每一框干货。