网络编程(4)socket主要函数接口、跨平台处理

首先介绍上一节示例中用到的一些socket编程的主要函数接口,包括函数入参、返回值。之后简单介绍跨平台下socket代码移植的处理方法。

1、socket主要函数接口

这里以linux下的函数为例说明,windows下接口类似,包含头文件有区别。本文下一节有说明。

1.1 创建套接字

#include <sys/socket.h>
int socket(int family, int type, int protocol); // 成功返回非负描述符,出错返回-1

参数family 指明协议簇,可取AF_INET、AF_INET6、AF_LOCAL等;
参数type 描述要建立的套接字的类型,如SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等;
参数protocol 指定套接字使用的特定协议,可选IPPROTO_TCP、IPPROTO_UDP,通常置为0选择默认连接方式即可。

1.2 绑定本地地址

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); //成功返回0,出错返回-1

bind()将所创建的套接字绑定到本地协议地址,协议地址是32位的IPv4地址(AF_INET)或128位的IPv6地址(AF_INET6)与16位的TCP或UDP端口的组合。

第二个参数myaddr是执行特定协议的地址指针结构,不同的协议可能导致第三个参数addrlen值(参数myaddr地址结构大小)不同。

bind()函数通常用于服务端需要调用该函数,以绑定到总所周知的的地址和端口;客户端并不需要这么做,在进行connet或者listen时内核会选择一个临时端口和ip地址客户端可以使用getsockname获取本地协议地址信息,服务端也可以通过getpeername获取客户端的协议地址信息

1.3 建立套接字连接

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); // 成功返回0,出错返回-1

参数同bind函数(),客户端与服务端建立连接。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *clientaddr, socklen_t* addrlen);  // 成功返回非负已连接套接字,出错返回-1

使用前已调用listen()函数,服务端从已完成连接的队列中返回下一个已完成连接,如果队列为空且阻塞则进入睡眠。参数clientaddr用于表示已连接的客户端的协议地址,参数addrlen用于确定地址结构(可传入NULL,不获取客户端协议地址)。返回值为已连接的套接字描述符

一个服务器通常只有一个监听套接字,在服务器生命周期一直存在。内核会为每个由服务器进程接受的客户连接创建一个已连接套接字(accept返回 值)。当服务器完成对某个客户的服务时,响应的已连接套接字就被关闭。

1.4 监听连接

#include <sys/socket.h>
int listen (int sockfd, int backlog);  // 成功返回0,出错返回-1

该函数仅TCP服务器使用,且需要在调用socket()和bind()之后使用,表示服务器意愿从当前的sockfd上接受客户端的请求,排队请求的个数最多为backlog( 最大监听个数宏 #define SOMAXCONN 128)。

1.5 数据传输

#include <unistd.h>
ssize_t read (int fd, void *buf, size_t n);
ssize_t write (int fd, const void *buf, size_t n);

从文件描述符fd中,读取n个字节到buf或写入buf的n个字节,成功返回实际的字节数,失败返回-1。在unix中,所有socket也当做文件,所以可以使用read或write读写socket数据

#include <sys/socket.h>
ssize_t send (int fd, const void *buf, size_t n, int flags);
ssize_t recv (int fd, void *buf, size_t n, int flags);

比read write多了一个参数flags,可以理解为比read write操作更细化的函数,但仅用于套接字。

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void* buff, size_t n, int flags, struct sockaddr* from, socklent_t* addr_len);
ssize_t sendto(int sockfd, void* buff, size_t n, int flags, const struct sockaddr* to, socklen_t addrlen);

前三个参数含义同函数send/recv,flags置为0,from\to 存放对端地址结构,addrlen地址结构长度。
recvfrom接收来自对端from的信息,并存于buff中;sendto发送信息buff到对端to。


一般这样使用:

socklen_t addr_len = sizeof(addr);
recvfrom(sockfd,buff,sizeof(buff),0,(struct sockaddr *)&addr,&addr_len);

(1)根据addr_len的值,判断客户端addr是地址类型是IPv4还是IPv6。
(2)对于UDP,recvfrom返回0是可以接受的;而TCP的read返回0则表示对端关闭连接
(3)如果不关心对端的协议地址,可以将recvfrom的最后两个参数置为空指针。
(4)recvfrom和sendto都可以用于TCP,但通常不这么做。
一般在UDP套接字中使用recvfrom/sendto;在TCP套接字中使用read/write.

2.6 关闭套接字

#include <unistd.h>
int close(int sockfd)
int shutdown(int sockfd)

close()关闭套接字sockfd,并释放当前进程分配给该套接字的资源;如果涉及一个打开的TCP连接,则该连接被释放。

shutdown是一种优雅地单方向或者双方向关闭socket的方法。 而close则立即双方向强制关闭socket并释放相关资源。

如果有多个进程共享一个socket,shutdown影响所有进程,而close只影响本进程。

2、跨平台处理

主要给出主要接口函数使用下的差异,并给出跨平台下的解决方式。

2.1 主要差异

  1. 头文件
    windows下winsock.h/winsock2.h
    linux下sys/socket.h 错误处理:errno.h
  2. 初始化、库加载
    Windows下需要初始化,而linux下不需要。
    程序开始需要调用初始化函数WSAStartup(),对应的退出清理用WSACleanup();
  3. socket类型
    windows下SOCKET
    linux下int
  4. 关闭socket
    windows下closesocket(…)
    linux下close(…)
  5. 获取错误码
    windows下WSAGetLastError()
    linux下errno变量extern int errno;
    int geterror(){return errno;}
  6. 设置非阻塞
    windows下
    int ul = 1
    ioctlsocket(server_socket,FIONBIO,&ul);
    linux下
    #include <fcntl.h>
    fcntl(server_socket,F_SETFL, O_NONBLOCK);
  7. 编译链接
    windows下需要链接ws2_32.lib库,inux下连接是使用参数:-lstdc。
    运行时需要libstdc++.so.5,可在/usr/lib目录中创建一个链接。属于系统环境,不需要额外处理。

2.2 解决方法

解决上述跨平台之间的代码差异,可以用如下代码简单解决。

#ifdef _WIN32

#if defined(_MSC_VER)
#ifdef _WIN64
using ssize_t = __int64;
#else
using ssize_t = int;
#endif

#include <io.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
#endif

using socket_t = SOCKET;

#else // not _WIN32


#include <sys/socket.h>
#include <netinet/in.h>           // sockaddr_in, inet_addr
#include <arpa/inet.h>			
#include <netdb.h>				
#include <unistd.h>				// close,shutdown, write, read
#include <cstring.h>

#endif

通过这种方式,再编写通用的socket函数。

int close_socket(socket_t sock) {
#ifdef _WIN32
  return closesocket(sock);
#else
  return close(sock);
#endif
}

int shutdown_socket(socket_t sock) {
#ifdef _WIN32
  return shutdown(sock, SD_BOTH);
#else
  return shutdown(sock, SHUT_RDWR);
#endif
}

void set_nonblocking(socket_t sock, bool nonblocking) {
#ifdef _WIN32
  auto flags = nonblocking ? 1UL : 0UL;
  ioctlsocket(sock, FIONBIO, &flags);
#else
  auto flags = fcntl(sock, F_GETFL, 0);
  fcntl(sock, F_SETFL,
        nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
#endif
}

bool get_error() {
#ifdef _WIN32
  return WSAGetLastError;
#else
  return errno;
#endif
}

源代码中添加初始化的代码

#ifdef _WIN32
class WSInit {
public:
  WSInit() {
    WSADATA wsaData;
    WSAStartup(0x0002, &wsaData);
  }

  ~WSInit() { WSACleanup(); }
};

static WSInit wsinit_; // 全局变量,windows下程序执行时构造初始化,退出时析构
#endif

有了以上通用代码的基础,其他的socket编程函数也能继续封装成跨平台的,那么项目代码的就具有可观的可移植性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aworkholic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值