问题聚焦:
    这节介绍的不仅是网络编程的几个API
    更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系。
    这节主要介绍三个方面的内容:套接字(socket地址)API,socket基础API,和网络信息API。

套接字API
套接字socket:(ip, port),即IP地址和端口对,唯一地表示了使用该TCP通信的一端。
需要了解:主机字节序和网络字节序。
原因:考虑32位的机器,CPU的累加器一次装载4字节的内容。那么这4字节在内存中的顺序将影响它被累加器装载后的所代表的含义。
分类:
  • 大端字节序:“高低,低高”,即,一个整数的高位字节(23~31位)存储在内存的低地址处,低位字节存储在内存的高地址处。
  • 小端字节序:“高高,低低”。
主机字节序:小端字节序。
网络字节序:大端字节序。
存在的问题:并不是所有的机器都采用主机字节序。一旦收发双方的字节序不同,必然导致错误的接收。
解决方法:发送端总是把要发送的数据转化成大端字节序,再发送。
注意:即使同一台机器上的两个进程,也要考虑字节序的问题,如一个客户端使用C语言编写,而另一个采用java编写(java虚拟机使用大端字节序)。
Linux提供的转换函数:
#inlcude<netinet/in.h>
unsigned long int htonl( unsigned long int hostlong );
unsigned short int htons( unsigned short int hostshort );
unsigned long int ntohl( unsigned long int netlong );
unsigned short int ntohs( unsigned short int netshort );

通用socket地址(结构体)
#include <bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
}
sa_family_da:地址族类型,与协议族类型对应。取值和对应关系如下图所示:
 
sa_data(char):存放socket地址。
协议族及其地址值:
 
数组长度有限,所以Linux提供了下面的新的通用socket地址结构体。
#include <bits/socket.h>
struct sockaddr_storage
{
sa_family_t sa_family;
unsigned long int __ssalign;
char __ss_padding[128-sizeof(__ss__align)];
}
__ss__align成员的作用:内存对齐。
专用socket地址
既然有通用socket地址,那么自然就有专用的socket地址。
专用:针对各个协议族的。
分类:
UNIX本地域协议族专用socket地址
#inlcude<sys/un.h>
struct sockaddr_un
{
sa_family_t sin_family; /* 地址族:AF_UNIX */
char sun_path[108]; /* 文件路径名 */
};
IPv4专用socket地址
#inlcude<sys/un.h>
struct sockaddr_in
{
sa_family_t sin_family; /* 地址族:AF_INET */
u_int16_t sin_port;
struct int_addr sin_addr; /* 文件路径名 */
};
struct in_addr
{
u_int32_t s_addr;
};
IPv6专用socket地址
#inlcude<sys/un.h>
struct sockaddr_in6
{
sa_family_t sin6_family; /* 地址族:AF_INET6*/
u_int16_t sin_port; /* 端口号,要用网络字节序表示 */
u_int32_t sin6_flowinfo; /* 流信息,应设置为0 */
struct int6_addr sin6_addr; /* IPv6地址结构体 */
u_int32_t sin6_scope_id; / * scope Id.,尚处于试验阶段 */
};
struct in_addr
{
unsigned char sa_addr[16]; /* IPv6地址,要用网络字节序表示 */
};
虽然Linux给我们提供了专用的socket地址,但是,在实际使用时,都要强制转换为通用socket地址类型
因为所有socket编程接口使用的地址参数的类型都是sockaddr。
IP地址转换函数
#include<arpa/inet.h>
in_addr_t inet_addr( const char* strptr ); /* 点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。失败时返回INADDR_NONE */
int inet_ation( const char* cp, struct in_addr* inp ); /* 完成和in_addr_t同样的功能,但是将转化结果存储于参数inp指向的地址结构中。 成功时返回1, 失败时返回0 */
char* inet_ntoa( struct in_addr in ); /* inet_ntoa函数将网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址 */
有一点需要注意,看下面的代码
char* szValue1 = inet_ntoa( "1.2.3.4" );
char* szValue2 = inet_ntoa( "10.194.71.60" );
printf( "address 1: %s\n", szValue1 );
printf( "address 2: %s\n", szValue2 ); //打印结果
address1: 10.194.71.60
address2: 10.194.71.60
原因:inet_ntoa是不可重入的。因为该函数内部用一个固定的静态变量存储结果,函数的返回值指向该内存。每次调用,都是写入相同的静态内存区,所以不可以多次计算。
同时适用于IPv4和IPv6地址的转换函数
#include <arpa/inet.h>
int inet_pton(int af, const char* src, void* dst);
const char* inet_ntop( int af, const void* src, char* dst, socklen, cnt);
函数说明:
inet_pton函数将用字符串表示的IP地址stc(用点分十进制表示的IPv4地址或用十六进制字符串表示的IPv6地址)转换成用网络字节序整数表示的IP地址,并把转换结果可存储与dst指向的内存中。
af: 指定地址族,可以是AF_INET或AF_INET6
inet_pton成功返回1,失败返回0并设置errno
inet_ntop函数进行相反的转换,前三个参数含义与inet_pton的参数相同,最后一个参数cnt指定目标存储单元的大小。
下面的两个宏能帮助我们指定这个大小
#include <netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
inet_ntop成功时返回目标存储单元的地址,失败则返回NULL并设置errno。 

创建socket
UNIX/Linux的一个哲学是:所有东西都是文件。socket也不例外,它就是可读、可写、可控制、可关闭的文件描述符。
创建一个socket
#include <sys/types.h>
#include <sys/socket.h>
int scoket( int domain, int type, int protocol );
函数说明:
domain: 告诉系统使用哪个底层协议。
type: 指定服务类型。服务类型主要有SOCK_STREAM服务(流服务,使用TCP协议)和SOCK_UGRAM(数据报,使用UDP协议)服务。
         还可以接受两个标志:SOCK_NONBLOCK(将新创建的socket设为非阻塞)和SOCK_CLOEXEC(用fork调用创建子进程时在子进程中关闭该socket)
protocol:在前两个参数构成的协议集合下,再选择一个具体的协议。几乎所有情况,把它设置为0.
成功时返回socket文件描述符,失败则返回-1并设置errno。

绑定socket
绑定:将一个上面创建的socket与一个具体的socket地址绑定
只有绑定了socket之后,客户端才能知道该如何连接它。
方式:匿名。通常操作系统会自动分配socket地址。
调用:bind
#include <sys/types.h>
#include <sys/socket.h>
int bind( int sockfd, const struct sockaddr* my_addr, socklen_t addrlen );
函数说明:
将my_addr所指的socket地址分配给未命名的sockfd文件描述符
addrlen:指出该socket地址的长度
成功时返回0,失败时返回-1并设置errno

监听socket
监听:socket绑定后依然不能马上接受客户连接,我们需要创建一个监听队列以存放待处理的客户连接。
调用:listen
#include <sys/socket.h>
int listen( int sockfd, int backlog );
函数说明:
sockfd:被监听的socket
backlog:提示内核监听队列的最大长度。backlog典型值是5,通常监听队列中完整连接的上限通常币backlog值略大。
成功时返回0,失败时返回-1并设置errno。

接受连接
调用:accept
#include <sys/types.h>
#include <sys/socket.h>
int accept( int sockfd, struct sockaddr *addr, socklen_t *addrlen );
作用:从listen监听队列中接受一个连接。服务器可以通过读写该socket来与被接受连接对应的客户端通信
函数说明:
sockfd:处于监听状态的socket。
addr:用来获取被接受连接的远端socket地址
addrlen:该socket地址的长度由addrlen指定。
成功返回0,失败返回-1并设置errno。

发起连接
调用:connect
#include <sys/types.h>
#include <sys/socket.h>
int connect( int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen );
函数说明:
sockfd:由socket调用返回一个socket
serv_addr:服务器监听的socket地址
addrlen:指定这个地址的长度
成功时返回0,失败则返回-并设置errno

关闭连接
关闭:close
#include <unist.h>
int close ( int fd );
函数说明:
fd:待关闭的socket。
注意:
close系统调用并非立即关闭一个连接,而是将fd的引用技术减1,只有当fd的引用计数为0时,才真正关闭连接。
多进程程序中,一次fork调用默认使父进程中打开的socket的引用计数加1,因此我们必须在父进程和子进程两种都对该socket执行close调用才能将该连接关闭
为了避免上面的麻烦,对于无论如何都要立即关闭连接,可以使用下面的调用:、
#include <sys/socket.h>
int shutdown ( int sockfd, int howto );
函数说明
sockfd:等待关闭的socket
howto:决定了shutdown的行为。可选值如下表:
    

数据读写
用于TCP流数据读写的调用:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv ( int sockfd, void *buf, size_t len, int flags );
ssize_t send ( int sockfd, const void *buf, size_t len, int flags );
函数说明:
recv读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小,flags一般设置为0。返回0说明连接关闭,返回-1出错并设置errno。
send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。成功时返回实际写入的数据的长度,失败则返回-1并设置errno。
flags参数为数据的收发提供了额外的控制。可以自行百度。
用于UDP数据报的读写
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom ( int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen );
ssize_t sendto ( int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen );
函数说明:
recvfrom读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小。因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址,即参数src_addr所指的内容,addrlen参数则指定该地址的长度。
sendto往sockfd上写入数据,buf和len分别指定写缓冲区的位置和大小,dest_addr指定接收端的socket地址,addrlen指定该地址的长度。
通用数据读写函数
#include <sys/socket.h>
ssize_t recvmsg( int sockfd, struct msghdr* msg, int flags );
ssize_t sendmsg( int sockfd, struct msghdr* msg, int flags );
函数说明:
sockfd:指定被操作的目标sockfd
msg:msghdr结构体类型的指针,定义如下:
 struct msghdr
{
void* msg_name; /* socket地址 */
socklen_t msg_namelen; /* socktet地址的长度 */
struct iovec* msg_iov; /* 分散的内存块 */
int msg_iovlen; /* 分散的内存块数量 */
void msg_control; /* 指向辅助数据的起始位置 */
socklen_t msg_controllen; /* 辅助数据的大小 */
int msg_flags; /* 复制函数中的flags参数,并在调用过程中更新 */
}; struct iovec
{
void *iov_base; /* 内存起始地址 */
size_t iov_len; /* 这块内存的长度 */
};

地址信息函数
功能:获取连接的本端socket地址,以及远端的socket地址。
函数:
#include <sys/socket.h>
int getsockname ( int sockfd, struct sockaddr* address, socklen_t* address_len ); /* 获取sockfd本端socket地址,并将其存储于address参数指定的内存中 */
int getpeername ( int sockfd, struct sockaddr* address, socklen_t* address_len ); /* 获取sockfd对应的远端socket地址,其参数及返回值的含义与getsockname的参数及返回值相同 */

socket选项函数
功能:用来读取和设置socket文件描述符属性的方法
函数:
#include <sys/scoket.h>
int getsockopt ( int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len );
int setsockopt ( int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);
socket选项表如下,需要的时候请按下表进行搜索:
 
 

小结:
    这章主要简要介绍了一些Linux网络编程基础的API,主要是提供索引的作用,后面将会通过实际的例子来使用这些API。
参考资料:
《Linux高性能服务器编程》

服务器编程入门(4)Linux网络编程基础API的更多相关文章

  1. Linux网络编程学习路线

    转载自:https://blog.csdn.net/lianghe_work/article 一.网络应用层编程   1.Linux网络编程01——网络协议入门 2.Linux网络编程02——无连接和 ...

  2. Linux网络编程一步一步学【转】

    转自:http://blog.chinaunix.net/uid-10747583-id-297982.html Linux网络编程一步一步学+基础  原文地址:http://blogold.chin ...

  3. Linux 高性能服务器编程——Linux网络编程基础API

    问题聚焦:     这节介绍的不仅是网络编程的几个API     更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系.     这节主要介绍三个方面的内容:套接字(so ...

  4. linux高性能服务器编程 (五) --Linux网络编程基础api

    第五章 Linux网络编程基础api 1.主机字节序和网络字节序 字节序是指整数在内存中保存的顺序.字节序分为大端字节序.小端字节序. 大端字节序:一个整数的高位字节数据存放在内存的低地址处.低位字节 ...

  5. Linux网络编程入门 (转载)

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  6. 【转】Linux网络编程入门

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  7. 《转》Linux网络编程入门

    原地址:http://www.cnblogs.com/duzouzhe/archive/2009/06/19/1506699.html (一)Linux网络编程--网络知识介绍 Linux网络编程-- ...

  8. linux网络编程基础--(转自网络)

    转自 http://www.cnblogs.com/MyLove-Summer/p/5215287.html Linux下的网络编程指的是socket套接字编程,入门比较简单. 1. socket套接 ...

  9. 第5章 Linux网络编程基础

    第5章 Linux网络编程基础 5.1 socket地址与API 一.理解字节序 主机字节序一般为小端字节序.网络字节序一般为大端字节序.当格式化的数据在两台使用了不同字节序的主机之间直接传递时,接收 ...

随机推荐

  1. PostgreSQL-pg_dump,pg_restore

    逻辑备份和psql一样,pg_dump.pg_restore有基本的和数据库连接的参数 -h 目标地址(对应环境变量$PGHOST) -p 连接端口(对应环境变量$PGPORT) -U 连接使用的用户 ...

  2. web前端开发CSS命名规范参考

    做为一个web前端工程师,每天接触HTML.css就像吃饭一样,但是作为一名合作.优秀的web前端工程师,对DIV+CSS命名还是有一定的规范的,本文整理了一份web前端开发中DIV+CSS各种命名规 ...

  3. 基于JQuery.timer插件实现一个计时器

    基于JQuery.timer插件实现一个计时器,需要的朋友可以参考下.   先去官网下载jQuery Timers插件 ,然后引用到html中.这里是1.2 version 复制代码代码如下: < ...

  4. Project Euler 89:Roman numerals 罗马数字

    Roman numerals For a number written in Roman numerals to be considered valid there are basic rules w ...

  5. 十大经典排序算法的JS版

    前言 个人博客:Damonare的个人博客 如遇到问题或有更好的优化方法,可以: 提issue给我 或是pull requests 我都会看到并处理,欢迎Star. 这世界上总存在着那么一些看似相似但 ...

  6. 《C++ Primer》学习笔记:向vector对象添加元素蕴含的编程假定

    练习<C++ Primer>中的3.14节时,当敲入: #include <iostream> #include <string> using namespace ...

  7. WEB 3D SVG CAD 向量 几个实施(转)

      一.他们所有的发展.从地上爬起来 VML+SVG发展矢量地图.你并不需要导入第三方的图片作为背景,直接在地图编辑器可以在底图内容编辑,由于岩石.巷道.煤层.画水.础地图样子再在其上面画出智慧线等设 ...

  8. 20155312 张竞予 Exp2 后门原理与实践

    Exp2 后门原理与实践 目录 基础问题回答 (1)例举你能想到的一个后门进入到你系统中的可能方式? (2)例举你知道的后门如何启动起来(win及linux)的方式? (3)Meterpreter有哪 ...

  9. xampp 忘记密码的处理方式.

    网上看到一些方法: 大部分是第一种:  方法一 这个方法, 我使用的时候没有生效. -------------- 后来看到另外一种方法 .  直接替换user表的三个文件.  这个方法成功了. xam ...

  10. java.lang.CharSequence cannot be resolved

    转自:http://jingyan.baidu.com/article/f25ef2546eace4482c1b82a9.html 方法/步骤 1 在MyEclipse中的配置方式为:右击项目-> ...