--------------------------------------------------------------------------------------------------------------------------------------------------

前言 - 思考还是

--------------------------------------------------------------------------------------------------------------------------------------------------

  socket 写过一点点,  总感觉很别扭. 例如 read, recv, recvfrom 这些为啥这么奇葩. 这是 linux 的设计吗.

这种强糅合的 read 代码, '带坏'了多少人. 想起很久以前看过的 <<UNIX痛恨者手册>>, 外加上常写点跨平台

库. 不得不思考设计, 发现

  1) winds 对于 socket 设计比 linux POSIX 设计理解更加友好一丢丢

  2) linux 性能比 winds 好. (开源哲学 对冲 精英文化)

  3) 应用层是个不完备的域, 不要一条胡同走不到头

(备注 : 有一段日子特别讨厌 winds, 及其喜欢羡慕 unix, 但是随着成长认识有了很大变化, 痛恨没钱没时间)

--------------------------------------------------------------------------------------------------------------------------------------------------

正文 - 来点证明

--------------------------------------------------------------------------------------------------------------------------------------------------

1. 如果可以不妨多写点跨平台, 线程安全的代码

  不妨举个烂大街的例子, 我们经常在处理时间的时候直接用  gettimeofday

#include <sys/time.h>

int gettimeofday(struct timeval * tv, struct timezone * tz);

The  functions  gettimeofday() can get and set the time as well as a timezone.
The tv argument is a struct timeval (as specified in <sys/time.h>): struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
}; and gives the number of seconds and microseconds since the Epoch (see time()).
The tz argument is a struct timezone: struct timezone {
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of DST correction */
}; If either tv or tz is NULL, the corresponding structure is not set or returned.
(However, compilation warnings will result if tv is NULL.) The use of the timezone structure is obsolete;
the tz argument should normally be specified as NULL.

只是简单的得到当前时间秒数和微秒, 附赠一个时区消息. 这个函数一眼看过去, 设计的不优美.

如果希望你的代码能够在 winds 上面也奔跑, 可能需要一个移植版本

#ifdef _MSC_VER

#include <winsock2.h>

//
// gettimeofday - Linux sys/time.h 中得到微秒的一种实现
// tv : 返回结果包含秒数和微秒数
// tz : 包含的时区,在winds上这个变量没有用不返回
// return : 默认返回0
//
inline int
gettimeofday(struct timeval * tv, void * tz) {
struct tm st;
SYSTEMTIME wtm; GetLocalTime(&wtm);
st.tm_year = wtm.wYear - ;
st.tm_mon = wtm.wMonth - ; // winds的计数更好些
st.tm_mday = wtm.wDay;
st.tm_hour = wtm.wHour;
st.tm_min = wtm.wMinute;
st.tm_sec = wtm.wSecond;
st.tm_isdst = -; // 不考虑夏令时 tv->tv_sec = (long)mktime(&st); // 32位使用数据强转
tv->tv_usec = wtm.wMilliseconds * ; // 毫秒转成微秒 return ;
} #endif

同样你的工作量已经起来了. 不管高不高效. 总是个下策. 这里有个更好的主意, 利用  timespec_get 

#include <time.h>

/* Set TS to calendar time based in time base BASE.  */
int
timespec_get (struct timespec *ts, int base)
{
switch (base)
{
case TIME_UTC:
if (__clock_gettime (CLOCK_REALTIME, ts) < )
return ;
break; default:
return ;
} return base;
}

C11 标准提供的获取秒和纳秒的时间函数, CL 和 GCC clang 都提供了支持. 上面是glibc中一个实现, 是不是很 low.

扯一点

  1.1 写代码应该有很强的目的, 非特殊领域应该弱化针对性

  1.2 上层应用, 应该首要向着标准靠拢, 其次是操作系统, 再到编译器

对于CL 实现了 timespec_get, 应该最主要目的是为了 C++11基础特性支持, 还有 clang 的实现.

--------------------------------------------------------------------------------------------------------------------------------------------------

2. 你是否和我一样曾经因为 WSAStartup 大骂微软SB

  写 socket winds 一定会有下面三部曲, 或者两部曲.

// 1. CL 编译器 设置
引入库 ws2_32.lib
引入宏 _WINSOCK_DEPRECATED_NO_WARNINGS // 2. 加载 socket dll
WSADATA wsad;
WSAStartup(WINSOCK_VERSION, &wsad); // 3. 卸载
WSACleanup

当时想, linux 为啥木有上面这么无意义的操作.  其实其中有个故事, 当初微软不得了时期, 无法和unix socket互连.

后面来回扯, 其它无数巨擎给其 Winsock 升级, dll 版本变化厉害. 所以有了上面抛给用户层加载绑定dll版本的操作.

那么再linux 上面真的不需要吗. 其实也需要, 只是在运行 _start 时候帮助我们做了. 所以这点上面完全可以这么

封装

//
// socket_init - 单例启动socket库的初始化方法
//
inline void socket_init(void) {
#ifdef _MSC_VER
WSADATA wsad;
WSAStartup(WINSOCK_VERSION, &wsad);
#elif __GUNC__
signal(SIGPIPE, SIG_IGN)
#endif
}

--------------------------------------------------------------------------------------------------------------------------------------------------

3. 还记得 read, recv, recvfrom 吗 ?

  还处在一切皆文件支配的恐惧中吗. 实现这种思路无外乎注册和switch工厂分支. 那就意味着 read 是个杂糅

体. 在我们只是使用 socket fd 读取的时候 最终 read -> recv 这个函数调用, 即 recv(fd, buf, sz, 0). 对于后者

ssize_t
__libc_recv (int fd, void *buf, size_t len, int flags)
{
#ifdef __ASSUME_RECV_SYSCALL
return SYSCALL_CANCEL (recv, fd, buf, len, flags);
#elif defined __ASSUME_RECVFROM_SYSCALL
return SYSCALL_CANCEL (recvfrom, fd, buf, len, flags, NULL, NULL);
#else
return SOCKETCALL_CANCEL (recv, fd, buf, len, flags);
#endif
}

可以表明 recv 和  recvfrom 实现层面有过纠缠. 但是和 read 上层没有耦合. 所以对于单纯 TCP socket 最好的

做法还是 recv 走起.

       #include <sys/types.h>
#include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

其中对于 recv flags 有下面几个多平台都支持的宏

#define MSG_OOB         0x1             /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */ #if(_WIN32_WINNT >= 0x0502)
#define MSG_WAITALL 0x8 /* do not complete until packet is completely filled */
#endif //(_WIN32_WINNT >= 0x0502)

其实开发中, MSG_OOB 带外数据, 除非学习. 否则无意义. MSG_PEEK 在以前的 \r\n 切分流协议的时候还用.

现在基本都没有场景. MSG_WAITALL 可以尝试一下替代很久以前的 for read. 可以有轻微提升性能.

recv(fd, buf, len, 0) or recv(fd, buf, len, MSG_WAITALL) 用在你的常说的'高性能'服务器中而不是大杂烩 read.

--------------------------------------------------------------------------------------------------------------------------------------------------

4. 是否为 listen, accept 好奇过 !

  首先从 listen 和 accept 一对好cp说起. 其实大体过程无外乎 listen -> connect -> accept .  这里只是从用法

而言首先看 listen 部分

/*
* Perform a listen. Basically, we allow the protocol to do anything
* necessary for a listen, and if that works, we mark the socket as
* ready for listening.
*/ SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn; err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed);
}
return err;
}

这段 listen 代码写得真好看. 我从中看出来, 内核的思路还是注册.  对于 backlog 存在一个最大值.

所以对于高性能服务器 listen 正确的写法推荐

listen(fd, SOMAXCONN)

把 listen创建的监听和链接成功队列大小交给操作系统的内核配置.

对于 accept 原本想讲一讲 accept4 + SOCK_NONBLOCK 降低 socket 开发流程. 但是一想起 unix or winds

应该不支持算了. 还是老实 accept + O_NONBLOCK.

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
int __user *, upeer_addrlen)
{
return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, );
}

突然意识到优化就是生命枯竭, 打击痛点才是王道.

--------------------------------------------------------------------------------------------------------------------------------------------------

5. 你为 select 苦恼过吗, 去它的 poll

  其实想想 select 这种函数设计的真的很奇葩. select -> poll -> epoll 从床上到床下经历过多少夜晚.

主要是 winds 和 linux 对于 select 完全是两个函数, 恰巧名字一样. 通过下面一个不好的材料了解

一个真正的客户端非阻塞的 connect

select 开发中的用法. 为什么讲 select, 因为方便 winds 移植调试 !! iocp很吊但是真的很难把它和epoll

揉在一起. 因为二者都很意外. epoll 是 61 + 10 分 一个iocp是 90 - 20 分. 如果强揉就要对 socket 行为

读写链接都需要抽出一层. 但是用 select 只需要抽出 poll 监听触发抽出来就可以了. 后期有时间我们

详细分析 iocp. 当前带大家感受下 epoll 那些操作.

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_create1(int flags); epoll_create() creates a new epoll() instance. Since Linux 2.6., the size argument is
ignored, but must be greater than zero; see NOTES below. epoll_create() returns a file descriptor referring to the new epoll instance. This file
descriptor is used for all the subse‐quent calls to the epoll interface. When no longer
required, the file descriptor returned by epoll_create() should be closed by using close().
When all file descriptors referring to an epoll instance have been closed, the kernel
destroys the instance and releases the associated resources for reuse. epoll_create1()
If flags is , then, other than the fact that the obsolete size argument is dropped,
epoll_create1() is the same as epoll_create(). The following value can be included in
flags to obtain different behavior: EPOLL_CLOEXEC
Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of
the O_CLOEXEC flag in open() for reasons why this may be useful.

更加具体是

SYSCALL_DEFINE1(epoll_create, int, size)
{
if (size <= )
return -EINVAL; return sys_epoll_create1();
}

从上面可以看出来目前推荐的 epoll_create 用法是

epoll_create1(EPOLL_CLOEXEC)

不再需要 size这个历史包袱, 并且 exec 重新开进程的时候能够 close 返回的 efd 防止句柄泄漏.

还有一个就是关于 epoll 的 EPOLLIN 默认LT水平触发状态, 另外一个是 EPOLLET 边缘触发.

/* Flags for epoll_create1.  */

#define EPOLL_CLOEXEC O_CLOEXEC

/* Valid opcodes to issue to sys_epoll_ctl() */

#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3 /* Epoll event masks */ #define EPOLLIN 0x00000001
#define EPOLLPRI 0x00000002
#define EPOLLOUT 0x00000004
#define EPOLLERR 0x00000008
#define EPOLLHUP 0x00000010 /* Set the Edge Triggered behaviour for the target file descriptor */ #define EPOLLET (1U << 31)

对于普通服务器例如游戏服务器, 大型Web系统服务器 LT 这种高级 select 操作就足够了.  刚好把验证

代码抛给上层. ET 模式的话就需要在框架的网络层处理包异常. 但是安全的高速度的通道通信可以尝试

一套ET流程交互. epoll 功能特别好理解, 注册, 监听, 返回结果. 最恶心就是返回结果的操作.

不妨展示个局部代码

//
// sp_wait - poll 的 wait函数, 等待别人自投罗网
// sp : poll 模型
// e : 返回的操作事件集
// max : e 的最大长度
// return : 返回待操作事件长度, <= 0 表示失败
//
int
sp_wait(poll_t sp, struct event e[], int max) {
struct epoll_event ev[max];
int i, n = epoll_wait(sp, ev, max, -); for (i = ; i < n; ++i) {
uint32_t flag = ev[i].events;
e[i].s = ev[i].data.ptr;
e[i].write = flag & EPOLLOUT;
e[i].read = flag & (EPOLLIN | EPOLLHUP);
e[i].error = flag & EPOLLERR;
} return n;
}

一个最简单的展示结果, 这里就处理了 EPOLLOUT 和 EPOLLHUP 还有 EPOLLERR 枚举.

EPOLLHUP 解决 listen -> connect -> accept 占用资源不释放, 空转问题. 其实想想最简单的TCP网络也不好搞.

要求很多 (网络细节, 是个大工程)

--------------------------------------------------------------------------------------------------------------------------------------------------

6. 讲的有点泛泛, 文末不妨展示个 不忘初心 

#include <stdio.h>
#include <limits.h>
#include <stdint.h> //
// 强迫症 × 根治
// file : len.c
// make : gcc -g -Wall -O2 -o love.out love.c
// test : objdump -S love.out
//
int main(int argc, char * argv[]) {
const char heoo[] = "Hello World"; for (size_t i = sizeof heoo - ; i < SIZE_MAX; --i)
printf(" %c", heoo[i]);
putchar('\n'); return ;
}

--------------------------------------------------------------------------------------------------------------------------------------------------

后记 - 力求走过

--------------------------------------------------------------------------------------------------------------------------------------------------

  错误是难免的欢迎指正.

昨日重现 : http://music.163.com/m/song?id=3986241&userid=16529894

The Carpenters - Yesterday Once[SD,854x480].mp4 : https://pan.baidu.com/s/1slA0yU5

socket 开发 - 那些年用过的基础 API的更多相关文章

  1. IOS socket开发基础

    摘要 详细介绍了iOS的socket开发,说明了tcp和udp的区别,简单说明了tcp的三次握手四次挥手,用c语言分别实现了TCPsocket和UDPsocket的客户端和服务端,本文的作用是让我们了 ...

  2. iOS的socket开发基础

    目录[-] socket简介 tcp和udp的区别 TCP三次握手和四次挥手 TCP三次握手 tcp四次挥手 tcpsocket和udpsocket的具体实现 tcpsocket的具体实现 udpso ...

  3. Android Socket 开发技术

    根据之前的经验,应用软件的网络通信无非就是Socket和HTTP,其中Socket又可以用TCP和UDP,HTTP的话就衍生出很多方式,基础的HTTP GET和POST请求,然后就是WebServic ...

  4. 一篇看懂Socket开发

    Socket[套接字]是什么,对于这个问题,初次接触的开发人员一般以为他只是一个通讯工具. Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发 T ...

  5. iOS蓝牙开发(二)蓝牙相关基础知识

    原文链接: http://liuyanwei.jumppo.com/2015/07/17/ios-BLE-1.html iOS蓝牙开发(一)蓝牙相关基础知识: 蓝牙常见名称和缩写 MFI ====== ...

  6. Android开发面试经——3.常见Java基础笔试题

      Android开发(29)  版权声明:本文为寻梦-finddreams原创文章,请关注:http://blog.csdn.net/finddreams 关注finddreams博客:http:/ ...

  7. Android开发面试经——2.常见Android基础笔试题

     标签: androidAndroid基础Android面试题Android笔试题 2015-03-12 15:04 3361人阅读 评论(3) 收藏 举报  分类: Android开发(29)  版 ...

  8. 【Xamarin开发 Android 系列 4】 Android 基础知识

    原文:[Xamarin开发 Android 系列 4] Android 基础知识 什么是Android? Android一词的本义指“机器人”,同时也是Google于2007年11月5日宣布的基于Li ...

  9. Socket开发

    Socket开发框架之消息的回调处理 伍华聪 2016-03-31 20:16 阅读:152 评论:0     Socket开发框架之数据加密及完整性检查 伍华聪 2016-03-29 22:39 阅 ...

随机推荐

  1. 【bzoj3796】Mushroom追妹纸 Kmp+二分+Hash

    题目描述 给出字符串s1.s2.s3,找出一个字符串w,满足: 1.w是s1的子串: 2.w是s2的子串: 3.s3不是w的子串. 4.w的长度应尽可能大 求w的最大长度. 输入 输入有三行,第一行为 ...

  2. 【bzoj2300】[HAOI2011]防线修建 离线+STL-set维护凸包

    题目描述 给你(0,0).(n,0).(x,y)和另外m个点,除(0,0)(n,0)外每个点横坐标都大于0小于n,纵坐标都大于0. 输入 第一行,三个整数n,x,y分别表示河边城市和首都是(0,0), ...

  3. ubuntu16.04上安装配置DHCP服务的详细过程

    DHCP服务器是为客户端机器分配IP地址的,所有分配的IP地址都保存在DHCP服务器的数据库中.为了在子网中实现DHCP分配IP地址,需要在目标主机上安装配置DHCP服务 1. 安装DHCP服务 安装 ...

  4. java学习2-webserver测试工具soapUI使用

    file-->new soap project-->输入project Name(随便)输入 WSDL地址,其他默认,点ok展开左侧加载的项目下的方法名,双击Request ,右侧出现测试 ...

  5. Linux集群--指定各个机器名字

    centOS7设置主机名:命令hostname可以查看当前主机名 虚招(临时的):重启机器后失效: hostname  XXX 实招(永久的): 招式一: 将/etc/sysconfig/networ ...

  6. Codeforces Round #423 (Div. 2, rated, based on VK Cup Finals) C 并查集

    C. String Reconstruction time limit per test 2 seconds memory limit per test 256 megabytes input sta ...

  7. chrome插件控制台

    在manifest.json中添加下面的几行 "background": { "scripts": ["background.js"] }, ...

  8. Adreno GPU Profiler工具使用总结

    Adreno Profiler介绍 Adreno Profiler 是高通公司开发的一款针对运行在高通骁龙处理器上用于图形和GPGPU技术应用的性能分析和帧调试工具.工具本质上是一个OpenGL ES ...

  9. libuv移植到ios

    libuv官网只提供了os x的编译方法,没有IOS的.既然os x和ios的系统内核差不多,并且编译工具都是xcode,那我们只要重新指定cpu架构,就可以编译出ios版的了. 1.安装python ...

  10. ASP.NET Core的身份认证框架IdentityServer4--入门【转】

    原文地址 Identity Server 4是IdentityServer的最新版本,它是流行的OpenID Connect和OAuth Framework for .NET,为ASP.NET Cor ...