Linux编程---套接字


网络相关的东西几乎都是建立在套接字之上.所以这个内容对于程序员来说还是蛮重要的啊.
 
其实套接字也就是一个特殊的设备文件而已,我始终不能明白为什么要叫套接字.这么个奇怪的名字.不过还是就这样算了吧.关键还是编程上.由于其重要性,我还是写的详细一点吧.

推荐阅读:

UNIX网络编程卷1:套接字联网API(第3版) 中文高清带完整书签 PDF 

Linux程序设计之套接字:UDP 

Linux内核--网络协议栈深入分析(五)--套接字的绑定、监听、连接和断开

Linux内核--网络协议栈深入分析(四)--套接字内核初始化和创建过程

Linux网络编程之套接字

Linux下tcp 和 udp 套接字收发缓冲区的大小决定规则
 
一.套接字
 
核心函数: int  socket(int domain,int type,int protocol);
 
这个函数在通信域domain中创建一个类型为type,使用协议protocol的套接字.并且返回一个描述字,也就是相当于打开了一个特殊的文件.可以用close,read,write等函数来操纵这个文件.其中domain估计对于只搞过java编程的估计都不知道是什么吧.最常见的有如下几种:
 
AF_UNIX: 这个指的是UNIX通信域,即同一台计算机的两个不同进程.也就是我在其他文章中,为进程间的通信用的socket.
 
AF_INET: 使用IPv4协议,这个用的就是我们常见的32位的IPv4地址
 
AP_INET6: 使用IPv6协议,要求的IP地址为128位的IPv6地址
 
其实这个一般应用都填0,由系统自动的选择默认协议.
 
 
 
然后是对于type的指定,主要有下面几种:
 
SOCK_STREAM: 流套接字,也就是所谓的TCP
 
SOCK_DGRAM: 数据报套接字,也就是所谓的UDP
 
SOCK_RAW: 这个可以越过高层协议直接访问底层协议,让程序员可以直接使用IP协议或网络的物理层.查看现存协议的实现细节就可以用这个.
 
并且对于这三个并不是可以随便搞的.第三个对于INT和INT6都是对应的IPv4和IPv6.但对于UNIX来说,这个就是没意义的.
 
对于第三个参数,其实只用写0就可以了.直接匹配默认协议.具体协议,用到后再查吧.
 
还有另外一种一次穿件两个socket的函数 socketpair.参数增加一个大小为2的整数数组,于返回两个文件描述符.这个用法和管道很像.
 
关闭:
 
这里关闭分为两种,一种是直接关闭这个描述字,另一种是断开其链接,
 
第一种用close大家都很熟悉,第二种用int shutdown(int socket,int how)
 
其中如何断开链接有下面三种方法:
 
SHUT_RD: 停止从套接字接收数据
 
SHUT_WR: 停止套接字传送数据
 
SHUT_RDWR: 完全停止.
 
二.套接字地址的结构
 
在系统中,32位的套接字就是个无符号整形变量.但是为了人能识别清楚,所以有了点分十进制记法的IP地址记法.所以就衍生出一些转换函数
 
inet_aton  inet_ntoa  inet_pton  inet_ntop
 
a表示IPv4的点分十进制,p表示的是IPv6的点分十进制.n表示二进制地址.
 
所以这四个函数分别是IPv4字符-二进制,二进制-IPv4字符,IPv6字符-二进制,二进制-IPv6字符.
 
并且还有一些特殊的地址记录在<netinet/in.h>
 
INADDR_LOOPBACK: 这个表示的是自己本机的地址,也就是回送地址,127.0.0.1
 
INADDR_ANY: 通配名,表示任何进入本机的地址
 INADDR_BROADCAST: 广播地址,也就是255.255.255.255
 
还有,由于不同主机有不同的IP地址,即便是点分十进制也是很难记住的.不如一个有意义的字符串好记.这个字符串就是我们常说的域名.学过计算机网络的应该就很清楚了,域名->DNS或本地域名系统->IP->主机->对方服务器.
 
具体的函数就是
 
struct hostent * gethostbyname(const char *name);
 
struct hostent * gethostbyaddr(const void *addr,size_t length,int type);
 
两个都是返回一个关于地址的结构体.其中包含:
 
主机的正式名字: char *h_name
 
主机的可选别名: char ** h_aliases
 
主机地址类型: int h_addrtype
 
每个地址的长度: int h_length;对于IPv4是4,IPv6为16
 
指向主机网络地址数组的指针: char ** h_addr_list;由于对方网站可能有多个IP,所以用list
 
第一个通过DNS或本地域名系统来访问.如果是DNS用的是UDP来请求.
 
第二个则是第一个参数是二进制的结构,不是字符串.但是我就搞不明白..为什么要一个size和一个type.写一个另一个不就清楚了吗?有了解的高手可以告诉我吗~
 
当需要读多个主机的地址信息时,可以先用sethostent打开主机地址数据库,然后用gethostent来逐个扫描登记项,再用endhostent来关闭数据库.
 
一定注意,这几个函数是不可重入的函数.一定要保证各个线程不会同时启动这几个函数.
 
这里的主机地址数据库我想就是本地域名系统吧.有时间关闭测试一下.
 
关于端口号:
 
每个套接字的地址都是由IP+PORT组成的.如果说IP是主机的身份认证,那么端口就是进程的认证.标准端口号小于IPPORT_RESERVED(通常为1024),只有根用户才能够执行的那些服务程序才能使用它们.这样就可以防止普通用户从标准端口接受数据来获取他人信息.而对于应用程序来说,一般端口号大于IPPORT_USERRESERVED(linux为5000).
 
如果使用的是没有指定地址的套接字时,系统为它自动生成一个应用端口号.很多客户端就是这么搞的,但是服务器端就不行了.
 
主要函数有:
 struct servent *getservbyname(const char *name,const char *proto);
 
struct servent *getservbyport(int port,const char *proto);
 
void setservent(int stayopen);
 
struct servent *getservent(void);
 
void endservent(void);
 
 
 
在UNIX系统有一个记录标准服务的数据库,这个数据库由文件/etc/services或域名服务器提供.定义在<netdb.h>中的结构体servent用于表示服务数据库的登记项信息.
 
servent结构有如下成员:
 服务程序的正式名字: char * s_name
 
服务器别名: char *s_aliases
 
服务程序对应的端口号: int s_port
 
与该服务一起使用的协议名: char *s_proto
 
所以简单来说,对于主机上的每个端口运行什么程序都在这个数据库中.
 
setservent则是开启这个服务器,必须开启才能使用这些函数.
 
getservbyname用来通过协议和名字来确定其servent项.getservbyport则通过端口.
 
getservent则是按照顺序一个个的读出数据库中的servent项.
 
endservent就是关闭这个服务数据库.
 
 
 
套接字地址数据结构:
 
1.对于进程通信的
 
struct sockaddr_un{
 sa_family_t sun_family; //这是地址族只能是AF_UNIX
 
char sun_path[108]; //Linux下是108
 
}
 
2.IPv4和IPv6
 
sockaddr_in{
 sa_family_t sin_famili; //AF_INET
 
in_prot_t sin_port; //16位端口号,网络字节序(大端)
 
struct in_addr sin_addr; //32位IP地址,网络字节序
 
unsigned char sin_zero[8]; //保留
 
}
 
sockaddr_in6{
 sa_family_t sin6_famili; //AF_INET6
 
in_prot_t sin6_port; //16位端口号,网络字节序(大端)
 
uint32_t  sin_flowinfo; //IPv6流标号和优先级信息,网络字节序
 
struct in6_addr sin6_addr;; //128位IPv6地址,网络字节序
 
}
 
3.通用版
 
struct sockaddr{
 sa_family_t sun_family; //这是地址族只能是AF_UNIX
 
char sun_path[108]; //Linux下是108
 
}
 
这个通常要将专用的特殊地质结构类型强制为struct sockaddr类型.
 
 
 
字节序:
 简单来说CPU字节序有大端有小端,为了在网上统一,所以采取大端.由于大端字节序通常比小端直观(反汇编过的都知道...),所以我认为这么选是为了调试帧信息的.
 
这里有4个函数来转换字节序:
 
unint16_t htons (uint16_t hostshort);
 
unint32_t htonl (uint16_t hostshort);
 
unint16_t ntohs (uint16_t hostshort);
 
unint32_t ntohl (uint16_t hostshort);
 
其中字母的意思为h为主机,n表示网络,s代表short,l代表long
 
hton表示从主机到网络.ntoh表示从网络到主机.
 
已经描述的很清楚了.
 
这里要注意的是,如果你通信的两个机子都是大端或者小端.字节序不改都可以,因为是相同的.但对于真正实际应用的服务器而言,则必须做转换,因为你不知道客户端到底是大端还是小端.

更多详情见请继续阅读下一页的精彩内容:

  • 1
  • 2
  • 下一页

相关内容