Chinaunix首页 | 论坛 | 博客
  • 博客访问: 189416
  • 博文数量: 54
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 630
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-02 18:41
文章分类

全部博文(54)

文章存档

2011年(1)

2009年(30)

2008年(23)

我的朋友

分类: C/C++

2009-01-22 17:39:04

参考自《Socket开发资料》

一、基本知识
主机字节序(Little endian和Big endian两种)和网络字节序(Big endian一种)
将多字节数据发送到网络上时,为网络统一处理,需转化为Big endian。
主机字节序和网络字节序转换的函数:
#include
uint16_t htons(uint16_t <16 位的主机字节序>)
uint32_t htonsl(uint32_t <32 位的主机字节序>) //转换为网络字节序
uint16_t ntohs(uint16_t <16 位的网络字节序>)
uint32_t ntohl(uint32_t <32 位的网络字节序>) //转换为主机字节序

缓冲区(TCP SOCKET有发送缓冲区和接收缓冲区,UDP SOCKET只有接收缓冲区)
TCP SOCKET具有流量控制,所以接收缓冲区的大小就是通知另一端的窗口的大小,对方不会发大于该窗口大小的数据;而UDP SOCKET 只有一个接收缓冲区无流量控制,当接收的数据报溢出时就会被丢弃。

通信域(地址族)
套接字存在于特定的通信域(即地址族)中,只有隶属于同一通信域(地址族)的套接字才能建立对话。Linux 支持AF_INET(IPv4 协议)、AF_INET6(IPv6 协议)和AF_LOCAL(Unix 域协议)。
套接口(socket)=网络地址+端口号。要建立一个套接口必须调用socket 函数。
定义一个连接的一个端点的两元组,即IP地址和端口号,称为一个套接口。
套接口有三种类型,即字节流套接口(SOCK_STREAM),数据报套接口(SOCK_DGRAM)和原始套接口(SOCK_RAW)。
在网络连接中,两个端点所组成的四元组(即本地IP、本地PORT、远程IP 和远程PORT)称为socket pair,该四元组唯一的标识了一个网络连接。该情况可通过netstat 验证。

 

二、 socket 地址结构

1. IPv4 的Socket 地址结构(定长)
    Struct in_addr{
        In_addr_t s_addr; // 32 位IP 地址,网络字节序

    }
    Struct sockaddr_in{
        Uint8_t sin_len;//IPv4 为固定的16 字节长度

        Sa_family_t sin_family; //地址簇类型,为AF_INET

        In_port_t sin_port; //16 位端口号,网络字节序

        Struct in_addr sin_addr; // 32 位IP 地址

        Char sin_zero[8]; //未用

    }

2. IPv6 的socket 地址结构(定长)
    struct in6_addr{
        uint8_t s6_addr[16]; //128 位IP 地址,网络字节序

    }
    struct sockaddr_in6{
        uint8_t sin6_len; //IPv6 为固定的24 字节长度

        sa_family_t sin6_family; //地址簇类型,为AF_INET6

        in_port_t sin6_port; //16 位端口号,网络字节序

        uint32_t sin6_flowinfo; //32 位流标签

        struct in6_addr sin6_addr; //128 位IP 地址

    }

3. UNIX 域socket 地址结构(变长)
    Struct sockaddr_un,地址簇类型为AF_LOCAL

4. 数据链路socket 地址结构(变长)
    struct sockaddr_dl,地址簇类型为AF_LINK

5. 通用的socket 地址结构
    struct sockaddr{
        uint8_t sa_len;
        sa_family_t sa_family;
        char sa_data[14];
    }


三、 C/S 网络编程
初始化socket连接符:
int socket(int domain, int type, int protocol);
函数返回socket 描述符,返回-1 表示出错
domain 参数只能取AF_INET, protocol 参数一般取0
应用示例:
TCP 方式:sockfd = socket(AF_INET,SOCK_STREAM,0);
UDP 方式:sockfd =socket(AF_INET, SOCK_DGRAM,0);

绑定端口:
int bind(int sockfd, struct sockaddr *sa, int addrlen);
函数返回-1 表示出错,最常见的错误是该端口已经被其他程序绑定。
需要注意的一点:在Linux 系统中,1024 以下的端口只有拥有root 权限的程序才能绑定。

连接网络(用于TCP方式):
int connect(int sockfd, struct sockaddr *servaddr, int addrlen);
函数返回-1 表示出错,可能是连接超时或无法访问。返回0 表示连接成功,可以通过sockfd 传
输数据了。

监听端口(用于TCP 方式):
int listen(int sockfd, int queue_length);
需要在此前调用bind()函数将sockfd 绑定到一个端口上,否则由系统指定一个随机的端口。
接收队列:一个新的Client 的连接请求先被放在接收队列中,直到Server 程序调用accept 函
数接受连接请求。
第二个参数queue_length,指的就是接收队列的长度 也就是在Server 程序调用accept 函数之
前最大允许的连接请求数,多余的连接请求将被拒绝。

响应连接请求(用于TCP 方式):
int accept(int sockfd,struct sockaddr *addr,int *addrlen);
accept()函数将响应连接请求,建立连接并产生一个新的socket 描述符来描述该连接,该连接
用来与特定的Client 交换信息。
函数返回新的连接的socket 描述符,错误返回-1

addr 将在函数调用后被填入连接对方的地址信息,如对方的IP、端口等。
addrlen 作为参数表示addr 内存区的大小,在函数返回后将被填入返回的addr 结构的大小。
accept 缺省是阻塞函数,阻塞直到有连接请求

应用示例:
struct sockaddr_in their_addr; /* 用于存储连接对方的地址信息*/
int sin_size = sizeof(struct sockaddr_in);
… …(依次调用socket(), bind(), listen()等函数)
new_fd = accept(sockfd, &their_addr, &sin_size);
printf(”对方地址: %s\n", inet_ntoa(their_addr.sin_addr));
… …

关闭socket 连接:
int close(int sockfd);
关闭连接将中断对该socket 的读写操作。
关闭用于listen()的socket 描述符将禁止其他Client 的连接请求。

部分关闭socket 连接:
int shutdown(int sockfd, int how);
Shutdown()函数可以单方面的中断连接,即禁止某个方向的信息传递。
参数how:
0 - 禁止接收信息
1 - 禁止发送信息
2 - 接收和发送都被禁止,与close()函数效果相同

socket 轮询选择:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
应用于多路同步I/O 模式(将在同步工作模式中详细讲解)
FD_ZERO(*set) 清空socket 集合
FD_SET(s, *set) 将s 加入socket 集合
FD_CLR(s, *set) 从socket 集合去掉s
FD_ISSET(s, *set) 判断s 是否在socket 集合中

常数FD_SETSIZE:集合元素的最多个数
等待选择机制:
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
是select 机制的一个变种,应用于多路同步I/O 模式(将在同步工作模式中详细讲解)
ufds 是pollfd 结构的数组,数组元素个数为nfds。
 struct pollfd {
  int fd; /* 文件描述字 */
  short events; /* 请求事件集合 */
  short revents; /* 返回时间集合 */
 };

接收/发送消息:
TCP 方式:
int send(int s, const void *buf, int len, int flags);
int recv(int s, void *buf, int len, int flags);
UDP 方式:
int sendto(int s, const void *buf, int len, int flags, const struct sockaddr *to, int tolen);
int recvfrom(int s,void *buf, int len, int flags, struct sockaddr *from, int *fromlen);
函数返回实际发送/接收的字节数,返回-1 表示出错,需要关闭此连接。
函数缺省是阻塞函数,直到发送/接收完毕或出错
注意:如果send 函数返回值与参数len 不相等,则剩余的未发送信息需要再次发送

UDP方式与TCP方式的区别:
需要指定发送/接收数据的对方(第五个参数to/from)
函数返回实际发送/接收的字节数,返回-1 表示出错。
函数缺省是阻塞函数,直到发送/接收完毕或出错
注意:如果send 函数返回值与参数len 不相等,则剩余的未发送信息需要再次发送

注意,网络字节流的读写不同于文件的读写,由于socket 缓冲的因素,可能读写的字节数小于所指定的字节数。所以可以使用如下函数:

    ssize_t readn(int fd, void * buf,size_t n)
    {
        ssize_t nleft;
        ssize_t nread;
        char *ptr;
        ptr = buf;
        nleft = n;
        while (nleft > 0)
        {
            if ((nread = read(fd, ptr, nleft)) < 0){
                if (errno == EINTR)
                    nread = 0;
                else
                    return (-1);
            }
            else if (nread == 0) //EOF

                break;
            nleft -= nread;
            ptr += nread;
        }
        return (n - nleft);
    }

    ssize_t written(int fd,const void *buf,size_t n)
    {
        ssize_t nleft;
        ssize_t nwrite;
        const char *ptr;
        ptr = buf;
        nleft = n;
        while (nleft > 0) {
            if (nwrite = write(fd, ptr, left) <= 0) {
                if (errno == EINTR)
                    nwrite = 0;
                else
                    return (-1);
            }
            nleft -= nwrite;
            ptr += nwrite;
        }
        return (n);
    }

基于消息的方式:
int sendmsg(int s, const struct msghdr *msg, int flags);
int recvmsg(int s, struct msghdr *msg, int flags);
标志位:
上面这六个发送/接收函数均有一个参数flags,用来指明数据发送/接收的标志,常用的标志主要有:
MSG_PEEK 对数据接收函数有效,表示读出网络数据后不清除已读的数据
MSG_WAITALL 对数据接收函数有效,表示一直执行直到buf 读满、socket 出错或者程序收到信号。
MSG_DONTWAIT 对数据发送函数有效,表示不阻塞等待数据发送完后返回,而是直接返回。(只对非阻塞socket 有效)
MSG_NOSIGNAL 对发送接收函数有效,表示在对方关闭连接后出错但不发送SIGPIPE 信号给程序。
MSG_OOB 对发送接收都有效,表示读/写带外数据(out-of-band data)

阅读(853) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~