文章目录
- 1. 服务端创建流程
- 2. 客户端创建流程
- 3. 常用API
- struct sockaddr 和 struct sockaddr_in
- net.core.somaxconn
- int socket(int family, int type, int protocol);
- int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- int listen(int sockfd, int backlog);
- int accept(int sockfd, struct sockaddr *client_addr, socklen_t *addrlen);
- int connect(int sock_fd, struct sockaddr *serv_addr, int addrlen);
- int recv(int sockfd, void *buf, int len, int flags);
- int send(int sockfd, const void *buf, int len, int flags);
- int close(int sockfd);
- int inet_pton(int af, const char *src, void *dst);
- const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- 字节序的转换函数
- 4. 服务端代码
- 5. 客户端代码
1. 服务端创建流程
- 调用socket函数创建监听socket;
- 调用bind函数将socket绑定到某个IP和端口号组成的二元组上;
- 调用listen函数开启监听;
- 当有客户端连接请求时,调用accept函数接受连接,产生一个新的socket(与客户端通信的socket);
- 基于新产生的socket调用send或recv函数开始与客户端进行数据交流;
- 通信结束后,调用close函数关闭socket。
2. 客户端创建流程
- 调用socket函数创建客户端socket;
- 调用connect函数尝试连接服务器;
- 连接成功后调用send或recv函数与服务器进行数据交流。
- 通信结束后,调用close函数关闭socket。
3. 常用API
struct sockaddr 和 struct sockaddr_in
这两个结构体都是用来处理网络通信的地址。
/*
*此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息
*note:
* 目标地址和端口信息在一起
*/
#include <sys/socket.h>
struct sockaddr
{
// 地址家族,一般“AF_xxx”的形式,通常使用AF_INET
unsigned short sa_family;
// 14字节协议地址,目标地址和端口信息
char sa_data[14];
}
#include <netinet/in.h>
struct sockaddr_in
{
short int sin_family; //协议族
unsigned short int sin_port; //端口号(使用网络字节顺序)
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //sockaddr与sockaddr_in 保持大小相同而保留的空字节
};
struct in_addr
{
unsigned long s_addr;
};
typedef struct in_addr
{
union
{
struct
{
unsigned char s_b1,
s_b2,
s_b3,
s_b4;
} S_un_b;
struct
{
unsigned short s_w1,
s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
} IN_ADDR;
sockaddr_in和sockaddr是并列的结构,指向sockaddr_in的结构体的指针,同样可以指向sockraddr的结构体,并代替它。
struct sockaddr_in mysock;
bind(sockfd, (struct sockaddr *)&mysock, sizeof(struct sockaddr); /* bind的时候进行转化 */
net.core.somaxconn
net.core.somaxconn是Linux中的一个kernel参数,表示socket监听(listen)的backlog上限。backlog是socket的监听队列,当一个请求(request)尚未被处理或建立时,他会进入backlog。而socket server可以一次性处理backlog中的所有请求,处理后的请求不再位于监听队列中。当server处理请求较慢,以至于监听队列被填满后,新来的请求会被拒绝。
在Hadoop 1.0中,参数ipc.server.listen.queue.size控制了服务端socket的监听队列长度,即backlog长度,默认值是128。而Linux的参数net.core.somaxconn默认值同样为128。当服务端繁忙时,如NameNode或JobTracker,128是远远不够的。这样就需要增大backlog,例如我们的3000台集群就将ipc.server.listen.queue.size设成了32768,为了使得整个参数达到预期效果,同样需要将kernel参数net.core.somaxconn设成一个大于等于32768的值。
Linux中可以工具syctl来动态调整所有的kernel参数。所谓动态调整就是kernel参数值修改后即时生效。但是这个生效仅限于os层面,对于Hadoop来说,必须重启应用才能生效。
显示所有的kernel参数及值
sysctl -a
修改参数值的语法
sysctl -w net.core.somaxconn=32768
以上命令将kernel参数net.core.somaxconn的值改成了32768。这样的改动虽然可以立即生效,但是重启机器后会恢复默认值。为了永久保留改动,需要用vi在/etc/sysctl.conf中增加一行
net.core.somaxconn=4000
然后执行命令
sysctl -p
int socket(int family, int type, int protocol);
-
作用:创建一个套接字描述符。在Linux系统中,一切皆文件。为了表示和区分已经打开的文件,Unix/Linux会给文件分配一个ID,这个ID是一个整数,被称为文件描述符。因此,网络连接也是一个文件,它也有文件描述符。通过socket()函数来创建一个网络连接或者说打开一个网络文件,socket()函数的返回值就是文件描述符,通过这个文件描述符我们就可以使用普通的文件操作来传输数据了。
-
参数:
family:指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;
type:套接字类型,主要 SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等;
protocol:一般取为0。 -
返回值:ok(非负),error(-1)。成功时,返回一个小的非负整数值,与文件描述符类似。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
作用:服务端把用于通信的地址和端口绑定到socket上。
-
参数:
sockfd:代表需要绑定的socket,即在创建socket套接字时返回的文件描述符;
addr:存放了服务端用于通信的地址和端口;
addrlen:代表addr结构体的大小。 -
返回值:当bind函数返回0时,为正确绑定;返回-1,则为绑定失败。
int listen(int sockfd, int backlog);
-
作用:listen函数的功能并不是等待一个新的connect的到来,真正等待connect的是accept函数。listen的操作就是当有较多的client发起connect时,server端不能及时处理连接请求,这时就会将connect连接放在等待队列中缓存起来。这个等待队列的长度由listen中的backlog参数来设定。
-
参数:
sockfd:前面socket创建的文件描述符;
backlog:指server端可以缓存连接的最大个数,也就是等待队列的长度。 -
返回值:当listen运行成功时,返回0;运行失败时,返回-1。
int accept(int sockfd, struct sockaddr *client_addr, socklen_t *addrlen);
-
作用:accept函数等待客户端的连接,如果没有客户端连上来,它就一直等待,这种方式称为阻塞。accept等待到客户端的连接后,创建一个新的socket,函数返回值就是这个新的socket,服务端用这个新的socket和客户端进行消息的收发。
-
参数:
sockfd:已经被listen过的socket;
client_addr:用于存放客户端的地址信息,其中包含客户端的协议族,网络地址以及端口号。如果不需要客户端的地址,可以填0;
addrlen:用于存放参数二(client_addr)的长度。 -
返回值:ok(新的 fd ),error(-1)。
int connect(int sock_fd, struct sockaddr *serv_addr, int addrlen);
-
作用:客户端向服务端发起连接请求。
-
参数:
sock_fd:代表通过socket()函数返回的文件描述符;
serv_addr:代表目标服务器的协议族,网络地址以及端口号,是一个sockaddr 类型的指针;
addrlen:代表第二个参数内容的大小。 -
返回值:当返回值是0时,代表连接成功;返回值为-1时,代表连接失败。
int recv(int sockfd, void *buf, int len, int flags);
-
作用:recv函数用于接收对端socket发送过来的数据。不论是客户端还是服务端,应用程序都用recv函数接受来自TCP连接的另一端发送过来的数据。如果socket对端没有发送数据,recv函数就会等待,如果对端发送了数据,函数返回接收到的字符数。
-
参数:
sockfd:代表接收端的套接字描述符,即通过socket()函数返回的文件描述符;
buf:用于接收数据的内存地址,可以是C语言基本数据类型变量的地址,也可以是数组、结构体、字符串;
len:指明需要接收数据的字节数。不能超过buf的大小,否则内存溢出;
flags:一般设置为0,其他数值意义不大。 -
返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是接收数据的长度。
int send(int sockfd, const void *buf, int len, int flags);
-
作用:send函数用于把数据通过socket发送给对端。不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据。
-
参数:
sockfd:代表发送端的套接字描述符,即通过socket()函数返回的文件描述符;
buf:指明需要发送数据的内存地址,可以是C语言基本数据类型变量的地址,也可以是数组、结构体、字符串;
len:指明实际发送数据的字节数;
flags:一般设置为0,其他数值意义不大。 -
返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是发送数据的长度。
int close(int sockfd);
-
作用:关闭套接字,并终止TCP连接。
-
参数:
sockfd:套接字描述符。
-
返回值:ok(0),error(-1)。
int inet_pton(int af, const char *src, void *dst);
-
作用:将文本地址转化为二进制地址。
-
头文件:#include <arpa/inet.h>
-
参数:
af:地址族。AF_INET:IPv4 地址;AF_INET6:IPv6 地址;
src:指向“点分式”IPv4 或 IPv6 地址的指针,例如“192.168.1.100”;
dst:类型为 struct in_addr *或者 struct in6_addr *的指针。 -
返回值:成功:1;失败:0 代表地址与地址族不匹配,-1 代表地址不合法。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
-
作用:将二进制地址转化为文本地址。
-
头文件:#include <arpa/inet.h>
-
参数:
af:地址族。AF_INET:IPv4 地址;AF_INET6:IPv6 地址;
src:类型为 struct in_addr *或者 struct in6_addr *的指针;
dst:地址缓冲区指针;
size:地址缓冲区大小,至少要 INET_ADDRSTRLEN 或者 INET6_ADDRSTRLEN 个字节。 -
返回值:成功:dst; 失败:NULL。
字节序的转换函数
-
函数原型:
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort); -
参数:
hostlong:主机字节序的长整型数据;
hostshort:主机字节序的短整型数据;
netlong: 网络字节序的长整型数据;
netshort:网络字节序的短整型数据。 -
返回值:对应的字节序数据。
4. 服务端代码
#include <iostream>
#include <unistd.h> // POSIX系统API访问
#include <sys/types.h> // 基本系统数据类型
#include <arpa/inet.h> // 网络信息转换
#include <string.h>
using namespace std;
#define SERVER_PORT 9991
int main() {
// 创建一个监听socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
cout << "create listen socket error!" << endl;
return -1;
}
// 初始化服务器地址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(SERVER_PORT);
//如果只想在本机上进行访问,bind函数地址可以使用本地回环地址
//如果只想被局域网的内部机器访问,那么bind函数地址可以使用局域网地址
//如果希望被公网访问,那么bind函数地址可以使用INADDR_ANY or 0.0.0.0
// 绑定地址和端口
if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1) {
cout << "bind listen socket error!" << endl;
return -1;
}
// 启动监听
if (listen(listenfd, SOMAXCONN) == -1) {
cout << "listen error!" << endl;
return -1;
}
cout << "start listening..." << endl;
while (true) {
// 创建一个临时的客户端socket
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
// 接受客户端连接
int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
if (clientfd != -1) {
char recvBuf[32] = {0};
// 从客户端接受数据
int ret = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
if (ret > 0) {
cout << "recv data from cilent successfully, data:" << recvBuf << endl;
// 将接收到的数据原封不动地发给客户端
ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
if (ret != strlen(recvBuf)) {
cout << "send data error!" << endl;
} else {
cout << "send data to client successfully, data:" << recvBuf <<endl;
}
} else {
cout << "recv data error!" <<endl;
}
}
close(clientfd);
}
// 关闭监听socket
close(listenfd);
return 0;
}
5. 客户端代码
#include <iostream>
#include <unistd.h> // POSIX系统API访问
#include <sys/types.h> // 基本系统数据类型
#include <arpa/inet.h> // 网络信息转换
#include <string.h>
using namespace std;
#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 9991
int main(int argc, char* argv[]) {
char* message;
if(argc != 2) {
fputs("Usage: ./client message\n", stderr); // 向指定的文件写入一个字符串
exit(1);
}
message = argv[1];
printf("send message: %s\n", message);
// 创建一个socket
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if (clientfd == -1) {
cout << "create client socket error!" << endl;
return -1;
}
// 连接服务器地址
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
serveraddr.sin_port = htons(SERVER_PORT);
if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
cout << "connect socket error!" << endl;
return -1;
}
// 向服务器发送数据
int ret = send(clientfd, message, strlen(message), 0);
if (ret != strlen(message)) {
cout << "send data error!" << endl;
return -1;
} else {
cout << "send data to server successfully, data:" << message << endl;
}
// 从服务器读取数据
char recvBuf[32] = {0};
ret = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
if (ret > 0) {
cout << "recv data from server successfully, data:" << recvBuf << endl;
} else {
cout << "recv data from server error!" << endl;
}
// 关闭socket
close(clientfd);
return 0;
}