分享

TCP协议套接字

 书永夜 2013-11-17
一.字节序
1.字节序是一个处理器架构特性,用于指示像整型数这样的大数据类型的内部字节顺序。
2.小端:低地址对应数据低位。
3.大端:高地址对应数据低位。
4.TCP/IP协议栈采用大端字节序。地址用网络字节序来表示。

二.转换函数
1.#include<arpa/inet.h> 或 <netinet/in.h>中
uint32_t htonl(uint32_t hostint32);返回值:以网络字节序表示的32位整形数。
uint16_t htons(uint16_t hostint16);返回值:以网络字节序表示的16位整形数。
uint32_t ntohl(uint32_t netint32);返回值:以主机字节序表示的32位整形数。
uint16_t ntohs(uint16_t netint16);返回值:以主机字节序表示的16位整形数。

2.地址格式
a.通用的地址结构
struct sockaddr
{
    sa_family_t sa_family;/*address family*/
    char sa_data[14];/*variable-length address*/
};
b.IPv4网域(AF_INET)
struct in_addr
{
    in_addr_t s_addr;/*ipv4 address*/
};

struct sockaddr_in
{
    sa_family_t sin_family;/*address family*/
    in_port_t   sin_port;/*port number*/
    struct in_addr sin_addr;/*ipv4 address*/
}

c.IPv6因特网域(AF_INET6)
struct in6_addr
{
    uint8_t s6_addr[16];/*IPv6 address*/
};

struct sockaddr_in6
{
    sa_family_t sin6_family;/*address family*/
    int_port_t  sin6_port;/*port number*/
    uint32_t    sin6_flowinfo;/*traffic class and flow info*/
    struct in6_addr sin6_addr;/*IPv6 address*/
    uint32_t    sin6_scope_id;/*set of interfaces for scope*/
};
3.地址转换
typedef uint32_t in_addr_t;

int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);//将"数字+句点"的格式的IP地址转换到uint32_t中,
//返回值已经是按照网络字节顺序的
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);//把类型为struct in_addr的数据转化为"数字+句点"
//的形式的字符串
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);

注意:inet_addr返回的整数形式是网络字节序,而inet_network返回的整数形式是主机字节序。
他俩都有一个小缺陷,那就是当IP是255.255.255.255时,这两个函数会认为这是个无效的IP地址,
这是历史遗留问题,其实在目前大部分的路由器上,这个255.255.255.255的IP都是有效的。
inet_aton函数和上面这俩个函数的区别就是在于他认为255.255.255.255是有效的,
他不会冤枉这个看似特殊的IP地址。对了,inet_aton函数返回的是网络字节序的IP地址。
综上所述,应该使用inet_aton和inet_ntoa这一对函数。


#include <sys/socket.h>
#include <neiinet/in.h>
#include <arpa/inet.h>

unsigned long inet_addr(const char * string);

这个函数使用string作为输入参数,并将这个点分十进制的IP地址转换为32位的二进制表示法,函数的返回值就是这个32位的二进制的网络字节序。当然如果string不是一个有效的点分十进制IP地址,函数返回INADDR_NONE。另外需要注意的是,当inet_addr函数返回INADDR_NONE的时候,它并没有建立一个有效的errno值,所以当函数返回错误的时候,不要去测试errno的值。

注意:在新程序中避免使用inet_addr函数,而应该使用inet_aton函数作为代替。因为对于inet_addr函数来说,即使输入的参数是有效的IP地址:255.255.255.255,他的返回值仍然是INADDR_NONE。

下面我们来说说inet_aton函数:  
#include <sys/socket.h>
#include <neiinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char* string, struct in_addr *addr);

参数string表示点分十进制IP地址的ASCII表示。输出参数addr是一个被新的IP地址跟新的结构。函数调用成功返回非0值。失败返回0.当然他也没有建立一个有效的errno值。

下面我们来看看inet_ntoa函数

有时候当用户连接到你的服务器的时候,需要知道他的IP地址,系统提供了inet_ntoa函数将32位的二进制IP地址表示转换为点分十进制的字符串形式:
#include <sys/socket.h>
#include <neiinet/in.h>
#include <arpa/inet.h>

char* inet_ntoa(struct in_addr addr);

需要注意的是inet_ntoa函数的返回值直到下次调用前一直有效。所以如果在线程中使用inet_ntoa的时候,一定要确保每次只有一个线程调用本函数。否则一个线程的返回的结构可能被其他线程返回的结果所覆盖。

接下来我们来看看inet_network函数。

当我们需要用网络掩码将IP地址中的网络位或者主机位提取出来的时候,如果能将点分十进制的IP地址转换为主机字节序的32位二进制IP地址形式就方便了,而inet_network函数的作用就是如此。
#include <sys/socket.h>
#include <neiinet/in.h>
#include <arpa/inet.h>

unsigned long inet_network(const char* addr);

函数的输入参数是存储在字符串addr中的点分十进制IP地址,返回值是主机字节序的32位二进制地址,但是如果输入参数不正确,返回值是0xFFFFFFFF.

以主机字节序的形式返回结果可以保证用户安全的使用网络掩码,因为如果返回值是网络字节序的话,那么不同的cPu平台所使用的网络掩码和程序代码就会有差异。
我们来看看inet_lnaof函数。

函数inet_lnaof函数是将套接口地址中的IP地址(网络字节序)转换为没有网络位的主机ID(主机字节序),这个函数为我们省去了很多的麻烦,因为我们不需要对IP地址进行分类,再将主机为从IP地址中提取出来。
#include <sys/socket.h>
#include <neiinet/in.h>
#include <arpa/inet.h>

unsigned long inet_lnaof(struct in_addr addr);

我们再来看看inet_netof函数

inet_lnaof函数返回的是主机ID,而inet_netof函数返回的是网络ID
#include <sys/socket.h>
#include <neiinet/in.h>
#include <arpa/inet.h>

unsigned long inet_netof(struct in_addr addr);

然后我们再来看看inet_makeaddr函数吧。根据之前的内容我们知道使用inet_netof函数和inet_lnaof函数,我们就可以把IP地址中的主机为和网络位分别提取出来,有时候我们还需要根据提取出来的主机位和网络为合并为一个新的IP地址。这个时候我们就可以使用inet_makeaddr函数。    
#include <sys/socket.h>
#include <neiinet/in.h>
#include <arpa/inet.h>

struct in_addr inet_makeaddr(int net , int host);

三.套接字相关函数
socket()

我们使用系统调用socket()来获得文件描述符:
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
第一个参数domain设置为“AF_INET”。
第二个参数是套接口的类型:SOCK_STREAM或
SOCK_DGRAM。第三个参数设置为0。
系统调用socket()只返回一个套接口描述符,如果出错,则返回-1。

bind()

一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。但如果你只想使用connect()则无此必要。
下面是系统调用bind()的使用方法:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,struct sockaddr *my_addr,int addrlen);
第一个参数sockfd是由socket()调用返回的套接口文件描述符。
第二个参数my_addr是指向数据结构sockaddr的指针。数据结构sockaddr中包括了关于你的地址、端口和IP地址的信息。
第三个参数addrlen可以设置成sizeof(struct sockaddr)。
下面是一个例子:
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#define MYPORT 3490
main()
{
    int sockfd;
    struct sockaddr_inmy_addr;
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    my_addr.sin_family=AF_INET;
    my_addr.sin_port=htons(MYPORT);
    my_addr.sin_addr.s_addr=inet_addr("132.241.5.10");
    bzero(&(my_addr.sin_zero),8);

    bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr));
    ...
}
如果出错,bind()也返回-1。
如果你使用connect()系统调用,那么你不必知道你使用的端口号。当你调用connect()时,它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。

connect()

系统调用connect()的用法如下:
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr *serv_addr,int addrlen);
第一个参数还是套接口文件描述符,它是由系统调用socket()返回的。
第二个参数是serv_addr是指向数据结构sockaddr的指针,其中包括目的端口和IP地址。
第三个参数可以使用sizeof(struct sockaddr)而获得。
下面是一个例子:
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#define DEST_IP "132.241.5.10"
#define DEST_PORT 23
main()
{
    intsockfd;
    struct sockaddr_in dest_addr;
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    dest_addr.sin_family=AF_INET;
    dest_addr.sin_port=htons(DEST_PORT);
    dest_addr.sin_addr.s_addr=inet_addr(DEST_IP);
    bzero(&(dest_addr.sin_zero),8);

    connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr));
    ...
}
同样,如果出错,connect()将会返回-1。

listen()

如果你希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理它们。这样,你通过首先调用listen(),然后再调用accept()来实现。
系统调用listen()的形式如下:
int listen(int sockfd,int backlog);
第一个参数是系统调用socket()返回的套接口文件描述符。
第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为20。你可以设置为5或者10。当出错时,listen()将会返回-1值。
当然,在使用系统调用listen()之前,我们需要调用bind()绑定到需要的端口,否则系统内核将会让我们监听一个随机的端口。所以,如果你希望监听一个端口,下面是应该使用的系统调用的顺序:
socket();bind();listen();accept()

系统调用accept()比较起来有点复杂。在远程的主机可能试图使用connect()连接你使用
listen()正在监听的端口。但此连接将会在队列中等待,直到使用accept()处理它。调用accept()
之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接
来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描
述符可以用来调用send()和recv()。
调用的例子如下:

#include<sys/socket.h>
int accept(int sockfd,void *addr,int *addrlen);
第一个参数是正在监听端口的套接口文件描述符。第二个参数addr是指向本地的数据结构
sockaddr_in的指针。调用connect()中的信息将存储在这里。通过它你可以了解哪个主机在哪个
端口呼叫你。第三个参数同样可以使用sizeof(structsockaddr_in)来获得。
如果出错,accept()也将返回-1。下面是一个简单的例子:
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#define MYPORT 3490
#define BACKLOG 10
main()
{
    intsockfd,new_fd;
    struct sockaddr_in my_addr;
    struct sockaddr_in their_addr;
    intsin_size;
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    my_addr.sin_family=AF_INET;
    my_addr.sin_port=htons(MYPORT);
    my_addr.sin_addr.s_addr=INADDR_ANY;
    bzero(&(my_addr.sin_zero),8);

    bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr));
    listen(sockfd,BACKLOG);
    sin_size=sizeof(struct sockaddr_in);
    new_fd=accept(sockfd,&their_addr,&sin_size);
}

四.建立服务器套接字的步骤:
//1.创建TCP协议类型的套接字(服务器端的本地套接字)
sfd = socket(AF_INET, SOCK_STREAM, 0);

//2.将本地套接字和IP地址及端口号绑定,建立一种关联
//类似于将手机卡上到手机里面
int AddrLen = sizeof(struct sockaddr_in);
printf("AddrLen=%d\n", AddrLen);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(8888);                //注意:网络字节序
ServerAddr.sin_addr.s_addr = inet_addr(IP);
int res = bind(sfd, (struct sockaddr *)&ServerAddr, AddrLen);

//3.设置服务器能够响应的最大连接数
//将这个套接字设置成监听模式
res = listen(sfd, 10);

//4.等待客户端的连接请求
//当accept返回时,会返回一个与客户端连接相关连的文件描述符
//第二个参数会被填上该客户的IP地址及端口号
struct sockaddr_in ClientAddr;
printf("AddrLen=%d\n", AddrLen);

五.建立客户端套接字
//1.创建TCP协议类型的套接字(服务器端的本地套接字)
int cfd = socket(AF_INET, SOCK_STREAM, 0);
ERROR(cfd < 0, "socket", err1);

//2.向服务器发送连接请求
//拨号!!
//填上服务器的IP地址及端口号
int AddrLen = sizeof(struct sockaddr_in);
printf("AddrLen=%d\n", AddrLen);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(8888);//注意:网络字节序
ServerAddr.sin_addr.s_addr = inet_addr(IP);
int res = connect(cfd, (struct sockaddr *)&ServerAddr, AddrLen);






    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多