0%

网络编程之 socket 相关函数

在网络编程中,socket 是核心内容,如果无法理解 socket 到底是什么,那么在做网络编程开发时,将会困难无比。在开始一个最简单的 TCP 例子之前,最好先了解一下什么是 socket,以及常用的与 socket 编程相关的函数。

什么是 socket?

socket是什么?在维基是这样说的:在计算机科学中,网络套接字(英语:Network socket),又译网络套接字、网络接口、网络插槽,是电脑网络中进程间数据流的端点。

在 Unix 中,一切都是文件,socket 也不例外,也是一个特殊的 “文件描述符”;所以对 socket 的也有类似文件的操作,如:readwriteclose 等。

socket 相关函数

socket

  1. 头文件:#include<sys/socket.h>

  2. 功能:建立一个协议族为 domain、协议类型为 type、协议编号为 protocolsocket 文件描述符。(当创建成功后,socket 在名称空间(网络地址族)中存在,但没有任何地址给它赋值。)在服务器端,socket()返回的套接字用于监听 (listen) 和接受 (accept) 客户端的连接请求,这个套接字不能用于与客户端之间发送和接收数据;在客户端则是用于与服务端之间发送和接收数据。

  3. 原型:

    1
    int socket(int domain, int type, int protocol);
  4. 参数:

    • domain:用于设置网络通信的域,该函数根据这个参数选择通信协议的族。

      名称 含义
      PF_UNIXPF_LOCAL 本地通信
      AF_INETPF_INET IPv4 Internet 协议
      PF_INET6 IPv6 Internet 协议
      PF_IPX IPX-Novell 协议
      PF_NETLINK 内核用户界面设备
      PF_X25 ITU-T X25 / ISO-8208 协议
      PF_AX25 Amateur radio AX.25
      PF_ATMPVC 原始 ATM PVC 访问
      PF_APPLETALK Appletalk
      PF_PACKET 底层包访问
    • type:用于设置套接字通信的类型。

      名称 含义
      SOCK_STREAM TCP 连接,提供序列化的、可靠的、双向连接的字节流。
      SOCK_DGRAM 支持 UDP 连接(无连接状态的消息)。
      SOCK_SEQPACKET 序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统数据需要将全部数据读出。
      SOCK_RAW RAW 类型,提供原始网络协议访问。
      SOCK_RDM 提供可靠的数据报文,不过可能数据会有乱序。
      SOCK_PACKET 这是一个专用类型,不能在通用程序中使用。
    • protocol:用于制定某个协议的特定类型,即 type 类型中的某个类型。(通常某协议中只有一种特定类型,这样 protocol 参数仅能设置为 0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。)

      • SOCK_STREAM 类型的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用 connect() 函数进行。一旦连接,可以使用 read() 或者 write() 函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收;当数据在一段时间内仍然没有接受完毕,可以将这个连接人为关闭。
      • SOCK_DGRAMSOCK_RAW 这个两种类型的套接字可以使用函数 sendto() 来发送数据到指定的 IP 地址;使用 recvfrom() 函数接收指定 IP 地址的数据。
      • SOCK_PACKET是一种专用的数据包,它直接从设备驱动接受数据。
  5. 返回值:

    • > 0:创建成功,返回一个标识这个 socket 的文件描述符。

    • -1: 创建失败;错误原因可以通过error获得。

      含义
      EACCES 没有权限建立制定的 domain 的 type 的 socket
      EAFNOSUPPORT 不支持所给的地址类型
      EINVAL 不支持此协议或者协议不可用
      EMFILE 进程文件表溢出
      ENFILE 已经达到系统允许打开的文件数量,打开文件过多
      ENOBUFS/ENOMEM 内存不足
      EPROTONOSUPPORT 制定的协议 type 在 domain 中不存在

bind(服务端)

  1. 头文件:#include<sys/socket.h>

  2. 功能:把用 addr 指定的地址赋值给用文件描述符代表的 sockfd;addrlen 指定了以 addr 所指向的地址结构体的字节长度。(该操作常称为“给socket命名”。)

  3. 原型:

    1
    int bind(int sockfd, const struct sockaddr *addr, socklen_t *addrlen);
  4. 参数:

    • sockfd:需要绑定地址的 socket。
    • addr:需要连接的地址;一般设置时使用 struct sockaddr_in,之后在使用到的函数参数中强制转换为 struct sockaddr *
    • addrlen:地址参数长度。
  5. 返回值:

    • 0:成功。

    • -1:出错,错误原因可以通过 error 获得。

      含义
      EACCESS 地址空间受保护,用户不具有超级用户的权限。
      EADDRINUSE 给定的地址正被使用。

listen(服务端)

  1. 头文件:#include<sys/socket.h>

  2. 功能:该函数使主动连接 socket 变为被动连接,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。(一般在调用 bind 之后和调用 accept 之前调用。)

  3. 原型:

    1
    int listen(int sockfd, int backlog);
  4. 参数:

    • sockfd:需要监听连接请求的 socket。
    • **backlog:**连接请求队列数(一般小于:30)。
  5. 返回值:

    • 0:成功。
    • -1:失败,错误原因可以通过 error 获得。

accept(服务端)

  1. 头文件:#include<sys/socket.h>

  2. 功能:接受客户端连接请求(阻塞式)。

  3. 原型:

    1
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  4. 参数:

    • sockf:监听等待连接的 socket。
    • addr:客户端 地址。
    • addrlen:地址长度。
  5. 返回值:

    • > 0:成功,返回一个新连接的客户端 (socket) 的文件描述符。
    • -1: 失败;错误原因可以通过error获得。

connect(客户端)

  1. 头文件:#include<sys/socket.h>****

  2. 功能:完成面向连接的协议的连接过程,对于TCP来说就是那个 三次握手 过程。

  3. 原型:

    1
    int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
  4. 参数:

    • sockfd:需要连接到服务端的 socket。
    • addr:服务端 地址。
    • addrlen:地址长度。
  5. 返回值:

    • 0:成功。

    • -1:失败,错误原因可以通过 error 获得。

      含义
      EACCESEPERM 用户试图在 socket 广播标志没有设置的情况下连接广播地址或由于防火墙策略导致连接失败。
      EADDRINUSE 本地地址处于使用状态。
      EAFNOSUPPORT 参数serv_add中的地址非合法地址。
      EAGAIN 没有足够空闲的本地端口。
      EALREADY 套接字为非阻塞 socket ,并且原来的连接请求还未完成。
      EBADF 非法的文件描述符。
      ECONNREFUSED 远程地址并没有处于监听状态。
      EFAULT 指向 socket 结构体的地址非法。
      EINPROGRESS socket 为非阻 socket,且连接请求没有立即完成。
      EINTR 系统调用的执行由于捕获中断而中止。
      EISCONN 已经连接到该 socket。
      ENETUNREACH 网络不可到达。
      ENOTSOCK 文件描述符不与 socket 相关。
      ETIMEDOUT 连接超时。

recv

  1. 头文件:#include <sys/socket.h>

  2. 原型:

    1
    ssize_t recv(int sock, void *buf, size_t len, int flags);
  3. 功能:接收数据(一般用于面向连接:TCP)。

  4. 参数说明:

    • sock: 将要接收数据的 socket。
    • buf: 存放数据的缓冲区。
    • len: 缓冲区大小。
    • flags: 一般设置为 0。
  5. 返回值:

    • > 0:成功,实际接收的字节数。
    • = 0:连接断开。
    • < 0:错误,错误原因可以通过 error 获得。

send

  1. 头文件:#include <sys/socket.h>

  2. 原型:

    1
    ssize_t send(int sock, const void *buf, size_t len, int flags); 
  3. 功能:发送数据(一般用于面向连接:TCP)。

  4. 参数说明:

    • sock: 将要发送数据的 socket。
    • buf: 发送数据的缓冲区。
    • len: 发送数据的大小。
    • flags: 一般设置为 0。
  5. 返回值:

    • > 0:成功,实际发送的字节数。
    • = 0:连接断开。
    • < 0:错误,错误原因可以通过 error 获得。

recvfrom

  1. 头文件:#include <sys/socket.h>

  2. 原型:

    1
    ssize_t recvfrom(int sock, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
  3. 功能:从指定地址接收数据(一般用于无连接:UDP)。

  4. 参数说明:

    • sock: 将要接收数据的 socket。
    • buf: 存放数据的缓冲区。
    • len: 缓冲区大小。
    • flags: 一般设置为 0。
    • from: 指定的来源地址。
    • fromlen: 来源地址长度。
  5. 返回值:

    • > 0:成功,实际接收的字节数。
    • = 0:连接断开。
    • < 0:错误,错误原因可以通过 error 获得。

sendto

  1. 头文件:#include <sys/socket.h>

  2. 原型:

    1
    ssize_t sendto (int sock, const void *buf, size_t len, unsigned int flags, const struct sockaddr *to, int tolen);
  3. 功能:发送数据到指定地址(一般用于无连接:UDP)。

  4. 参数说明:

    • sock: 将要发送数据的 socket。
    • buf: 发送数据的缓冲区。
    • len: 发送数据的大小。
    • flags: 一般设置为 0。
    • to: 指定的目的地址。
    • tolen: 目的地址长度。
  5. 返回值:

    • > 0:成功,实际发送的字节数。
    • = 0:连接断开。
    • < 0:错误,错误原因可以通过 error 获得。

recvmsg

  1. 头文件:#include <sys/socket.h>

  2. 原型:

    1
    ssize_t recvmsg(int sock, struct msghdr *msg, int flags);
  3. 功能:从指定地址接收数据(一般用于无连接:UDP)。

  4. 参数说明:

    • sock: 将要接收数据的 socket。

    • msg: 数据结构体。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      struct msghdr {
      void *msg_name; /* protocol address */
      socklen_t msg_namelen; /* sieze of protocol address */
      struct iovec *msg_iov; /* scatter/gather array */
      int msg_iovlen; /* # elements in msg_iov */
      void *msg_control; /* ancillary data ( cmsghdr struct) */
      socklen_t msg_conntrollen; /* length of ancillary data */
      int msg_flags; /* flags returned by recvmsg() */
      }

      struct iovec {
      void *iov_base; /* starting address of buffer */
      size_t iov_len; /* size of buffer */
      }
    • flags:一般设置为 0。

  5. 返回值:

    • > 0:成功,实际接收的字节数。
    • = 0:连接断开。
    • < 0:错误,错误原因可以通过 error 获得。

sendmsg

  1. 头文件:#include <sys/socket.h>

  2. 原型:

    1
    ssize_t sendmsg(int sock, const struct msghdr *msg, int flags);
  3. 功能:发送数据到指定地址(一般用于无连接:UDP)。

  4. 参数说明:

    • sock: 将要发送数据的 socket。
    • msg:数据结构体。
    • flags:一般设置为 0。
  5. 返回值:

    • > 0:成功,实际发送的字节数。
    • = 0:连接断开。
    • < 0:错误,错误原因可以通过 error 获得。

TCP 最简单的例子

该例子中只是简单的完成了“服务端与客户端的连接”,“服务端与客户端相互发送及回应消息”;但主要目的是了解以及掌握常见的与 socket 相关的函数。

Server

Server.hpp

1
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
#ifndef TCPServer_hpp
#define TCPServer_hpp

#include <iostream> // cout
#include <sys/socket.h> // socket
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // close


#define BUFF_SIZE 4096


/** 返回值 枚举 */
typedef enum __retValue {
Ret_error = -2, // 出错
Ret_failed = -1, // 失败
Ret_success = 0, // 成功
}RetValue;


class TCPServer
{
private:
struct sockaddr_in serverAddr; // 服务端地址
socklen_t serAddrLen; // 地址长度
int serverSockfd; // 服务端 socket

public:
TCPServer(std::string ipStr, unsigned int port);
~TCPServer();

/**
绑定 服务端 地址

@return 参见‘RetValue’
*/
RetValue BindAddr();

/**
监听端口连接请求

@param queueNum 最大等待处理连接队列数
@return 参见‘RetValue’
*/
RetValue Listen(unsigned int queueNum);

/**
运行服务端

@return 参见‘RetValue’
*/
RetValue Run();
};

#endif /* TCPServer_hpp */

Server.cpp

1
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
#include "TCPServer.hpp"


#define DEFAULT_QUEUE_NUM 5


TCPServer::TCPServer(std::string ipStr, unsigned int port)
{
if (true == ipStr.empty()
|| 0 >= ipStr.length())
{
std::cout << "Ip 地址不能为空!" << std::endl;
return;
}
// 创建 服务端 socket
serverSockfd = socket(PF_INET, SOCK_STREAM, 0);
if (0 > serverSockfd)
{
std::cout << "创建 socket 失败!" << std::endl;
return;
}
std::cout << "创建 socket 成功!" << std::endl;

// 设置 服务端 地址
serAddrLen = sizeof(serverAddr);
memset(&serverAddr, 0, serAddrLen); // 清空结构体
serverAddr.sin_family = AF_INET; // 协议族 类型
serverAddr.sin_port = htons(port); // 服务端 端口
serverAddr.sin_addr.s_addr = inet_addr(ipStr.c_str()); // 服务端 IP 地址
}


TCPServer::~TCPServer()
{
if (0 < serverSockfd)
{
std::cout << "关闭服务端 sockfd !" << std::endl;
close(serverSockfd); // 关闭服务端 sockfd
}
}


RetValue TCPServer::BindAddr()
{
// 绑定 IP 地址和端口号
int ret = bind(serverSockfd, (struct sockaddr*)&serverAddr, serAddrLen);
if (0 != ret)
{
std::cout << "绑定 IP 地址和端口失败!" << std::endl;
return Ret_failed;
}
std::cout << "绑定 IP 地址和端口成功!" << std::endl;
return Ret_success;
}


RetValue TCPServer::Listen(unsigned int queueNum)
{
// 监听端口(使 Socket 变为被动连接,等待客户端 Socket 连接)
int ret = listen(serverSockfd, 0 >= queueNum ? DEFAULT_QUEUE_NUM : queueNum);
if (0 != ret)
{
std::cout << "监听端口失败!" << std::endl;
return Ret_failed;
}
std::cout << "监听端口成功!" << std::endl;
return Ret_success;
}


RetValue TCPServer::Run()
{
// 客户端 地址
struct sockaddr_in clientAddr;
socklen_t cliAddrLen = sizeof(clientAddr);

// 阻塞式等待客户端连接
std::cout << "等待客户端连接。。。" << std::endl;
int clientSocket = accept(serverSockfd, (struct sockaddr*)&clientAddr, &cliAddrLen);
if (0 > clientSocket)
{
std::cout << "服务器接受客户端连接失败!" << std::endl;
return Ret_failed;
}
std::cout << "服务器接受客户端连接成功!" << std::endl;

// 接收消息
char recvBuf[BUFF_SIZE];
memset(recvBuf, 0, BUFF_SIZE);
ssize_t recvLen = recv(clientSocket, recvBuf, BUFF_SIZE, 0);
if (0 > recvLen) // 出错
{
std::cout << "接收消息出错!" << std::endl;
return Ret_error;
}
else if (0 == recvLen) // 连接已断开
{
std::cout << "接收消息失败,连接已断开!" << std::endl;
return Ret_failed;
}
else // 接收成功
{
recvBuf[recvLen] = '\0';
std::cout << "接收到服务端消息:" << recvBuf << std::endl;
}

// 发送消息
std::string msg = "Hi client, I'm server !";
ssize_t sendLen = send(clientSocket, msg.c_str(), msg.length(), 0);
if (0 > sendLen) // 出错
{
std::cout << "发送消息出错!" << std::endl;
return Ret_error;
}
else if (0 == sendLen) // 连接已断开
{
std::cout << "发送消息失败,连接已断开!" << std::endl;
return Ret_failed;
}
else // 发送成功
{
std::cout << "发送消息成功!" << std::endl;
}
sleep(3);

// 关闭客户端 Socket
close(clientSocket);

return Ret_success;
}

Server.main

1
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
#include <iostream>
#include "TCPServer.hpp"


#define DEMO_PORT 5578
#define DEMO_IP "127.0.0.1"
#define MAX_LISTEN_QUEUE 10

int main()
{
TCPServer server(DEMO_IP, DEMO_PORT);
if (Ret_success != server.BindAddr())
{
exit(1);
}
if (Ret_success != server.Listen(MAX_LISTEN_QUEUE))
{
exit(1);
}
if (Ret_success != server.Run())
{
exit(1);
}

return 0;
}

Client

Client.hpp

1
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
#ifndef TCPClient_hpp
#define TCPClient_hpp

#include <iostream> // cout
#include <sys/socket.h> // socket
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // close


#define BUFF_SIZE 4096


/** 返回值 枚举 */
typedef enum __retValue {
Ret_error = -2, // 出错
Ret_failed = -1, // 失败
Ret_success = 0, // 成功
}RetValue;


class TCPClient
{
private:
struct sockaddr_in serverAddr; // 服务端地址
socklen_t serAddrLen; // 地址长度
int clientSockfd; // 客户端 socket

public:
TCPClient(std::string ipStr, unsigned int port);
~TCPClient();

/**
连接 服务端

@return 参见‘RetValue’
*/
RetValue ConnServer();

/**
运行 客户端

@return 参见‘RetValue’
*/
RetValue Run();
};

#endif /* TCPClient_hpp */

Client.cpp

1
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
#include "TCPClient.hpp"


TCPClient::TCPClient(std::string ipStr, unsigned int port)
{
if (true == ipStr.empty()
|| 0 >= ipStr.length())
{
std::cout << "Ip 地址不能为空!" << std::endl;
return;
}
// 创建 客户端 socket
clientSockfd = socket(PF_INET, SOCK_STREAM, 0);
if (0 > clientSockfd)
{
std::cout << "创建 socket 失败!" << std::endl;
return;
}
std::cout << "创建 socket 成功!" << std::endl;

// 设置 服务端 地址
serAddrLen = sizeof(serverAddr);
memset(&serverAddr, 0, serAddrLen); // 清空结构体
serverAddr.sin_family = AF_INET; // 协议族 类型
serverAddr.sin_port = htons(port); // 服务端 端口
serverAddr.sin_addr.s_addr = inet_addr(ipStr.c_str()); // 服务端 IP 地址
}


TCPClient::~TCPClient()
{
if (0 < clientSockfd)
{
std::cout << "关闭客户端 sockfd !" << std::endl;
close(clientSockfd); // 关闭客户端 sockfd
}
}


RetValue TCPClient::ConnServer()
{
std::cout << "开始连接 服务端。。。" << std::endl;
// 连接 服务端
int ret = connect(clientSockfd, (struct sockaddr*)&serverAddr, serAddrLen);
if (0 != ret)
{
std::cout << "连接 服务端 失败!" << std::endl;
return Ret_failed;
}
std::cout << "连接 服务端 成功!" << std::endl;
return Ret_success;
}


RetValue TCPClient::Run()
{
// 发送消息
std::string msg = "Hello server, I'm client !";
ssize_t sendLen = send(clientSockfd, msg.c_str(), msg.length(), 0);
if (0 > sendLen) // 出错
{
std::cout << "发送消息出错!" << std::endl;
return Ret_error;
}
else if (0 == sendLen) // 连接已断开
{
std::cout << "发送消息失败,连接已断开!" << std::endl;
return Ret_failed;
}
else // 发送成功
{
std::cout << "发送消息成功!" << std::endl;
}

// 接收消息
char recvBuf[BUFF_SIZE];
ssize_t recvLen;
memset(recvBuf, 0, BUFF_SIZE);
recvLen = recv(clientSockfd, recvBuf, BUFF_SIZE, 0);
if (0 > recvLen) // 出错
{
std::cout << "接收消息出错!" << std::endl;
return Ret_error;
}
else if (0 == recvLen) // 连接已断开
{
std::cout << "接收消息失败,连接已断开!" << std::endl;
return Ret_failed;
}
else // 接收成功
{
recvBuf[recvLen] = '\0';
std::cout << "接收到服务端消息:" << recvBuf << std::endl;
}
sleep(3);
return Ret_success;
}

Client.main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include "TCPClient.hpp"


#define DEMO_PORT 5578
#define DEMO_IP "127.0.0.1"


int main()
{
TCPClient client(DEMO_IP, DEMO_PORT);
if (Ret_success != client.ConnServer())
{
exit(1);
}
if (Ret_success != client.Run())
{
exit(1);
}

return 0;
}

Demo下载