【笔记】socket网络编程

一. socket

每一个 Socket 都用一个半相关描述: {协议,本地地址,本地端口}
一个完整的 Socket 则用一个相关描述 {协议,本地地址,本地端口,远程地址,远程端口}
每一个 Socket 有一个本地的唯一 Socket 号,由操作系统分配。


1. 套接字的三种类型

流式套接字 (SOCK_STREAM )     数据报套接字(SOCK_DGRAM)    原始套接字


无链接服务器一般都是面向事物处理到,一个请求一个应答就完成客户程序与服务程序之间的相互作用


套接字工作过程如下:服务器首先启动,通过调用 socket()建立一个套接字,然后调用
bind()将该套接字和本地网络地址联系在一起,再调用 listen()使套接字做好侦听的准备,
并规定它的请求队列的长度,之后就调用 accept()来接收连接。客户在建立套接字后就可调
用 connect()和服务器建立连接。连接一旦建立,客户机和服务器之间就可以通过调用 read()
和 write()来发送和接收数据。最后,待数据传送结束后,双方调用 close()关闭套接字。



二. 套接字的一些基本知识


1. 基本结构

struct sockaddr  这个结构用来存储套接字地址

struct sockaddr {
    unsigned short sa_family;   /* address族, AF_xxx */   sa_family 一般来说,都是 “AFINET”。
    char sa_data[14];                  /* 14 bytes 的协议地址 */
};


struct sockaddr_in {   (“in” 代表 “Internet”)

    short int sin_family;                 /* Internet地址族 */
    unsigned short int sin_port;   /* 端口号 */
    struct in_addr sin_addr;         /* Internet地址 */
    unsigned char sin_zero[8];    /* 添0(和struct sockaddr一样大小)*/
};

struct in_addr {      因特网地址
     unsigned long s_addr;
};


下面给出套接字字节转换程序的列表:

  1. htons()——“Host to Network Short” 主机字节顺序转换为网络字节顺序(对无符号短型进行操作 4 bytes)
  2. htonl()——“Host to Network Long”  主机字节顺序转换为网络字节顺序(对无符号长型进行操作 8 bytes)
  3. ntohs()——“Network to Host Short “  网络字节顺序转换为主机字节顺序(对无符号短型进行操作 4 bytes)
  4. ntohl()——“Network to Host Long “  网络字节顺序转换为主机字节顺序(对无符号长型进行操作 8 bytes)


IP 地址转换

  • 首先,让我假设你有一个 struct sockaddr_in ina,并且你的 IP 是 166.111.69.52 ,你想把你的 IP 存储到 ina 中。你可以使用的函数: inet_addr() ,它能够把一个用数字和点表示 IP 地址的字符串转换成一个无符号长整型.inet_addr() 返回的地址已经是网络字节顺序了,你没有必要再去调用 htonl() 函数,

        ina.sin_addr.s_addr = inet_addr(“166.111.69.52” )

  • 有一个 struct in_addr 并且你想把它代表的 IP 地址打印出来(按照 数字.数字.数字.数字的格式)......

        这里,你可以使用函数 inet_ntoa()(“ntoa”代表“Network to ASCII”):
        printf(“%s”, inet_ntoa(ina.sin_addr));

  • inet_ntoa() 使用 struct in_addr 作为一个参数,不是一个长整型值。

        inet_ntoa() 返回一个字符指针,它指向一个定义在函数 inet_ntoa() 中的 static 类型字符串。所以每次你调用 inet_ntoa(),都会改变最后一次调用 inet_ntoa() 函数时得到的结果。
比如:
char *a1, a2;
a1 = inet_ntoa(ina1.sin_addr); /* this is 166.111.69.52 */
a2 = inet_ntoa(ina2.sin_addr); /* this is 166.111.69.53 */
printf(“address 1: %s\n”,a1);
printf(“address 2: %s\n”,a2);
将会显示出:
address 1: 166.111.69.53
address 2: 166.111.69.53


socket() 函数

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain , int type , int protocol);

int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;

int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);
connect()的三个参数意义如下:
l sockfd :套接字文件描述符,由 socket()函数返回的。
l serv_addr 是一个存储远程计算机的 IP 地址和端口信息的结构。
l addrlen 应该是 sizeof(struct sockaddr)。

int listen(int sockfd, int backlog);


int accept(int sockfd, void *addr, int *addrlen);

  • accept()函数的参数意义如下:sockfd 是正在 listen() 的一个套接字描述符。
  • addr 一般是一个指向 struct sockaddr_in 结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的 IP 地址和端口)。
  • addrlen 是 一 个 本 地 的 整 型 数 值 , 在 它 的 地 址 传 给 accept() 前 它 的 值 应 该 是sizeof(struct sockaddr_in);accept()不会在 addr 中存储多余 addrlen bytes 大小的数据。如果accept()函数在 addr 中存储的数据量不足 addrlen,则 accept()函数会改变 addrlen 的值来反应这个情况

int send(int sockfd, const void *msg, int len, int flags);

  • sockfd 是代表你与远程程序连接的套接字描述符。
  • msg 是一个指针,指向你想发送的信息的地址。
  • len 是你想发送信息的长度。
  • flags 发送标记。一般都设为 0(你可以查看 send 的 man pages 来获得其他的参数值并且明白各个参数所代表的含义)。

int recv(int sockfd, void *buf, int len, unsigned int flags);

  • sockfd 是你要读取数据的套接字描述符。
  • buf 是一个指针,指向你能存储数据的内存缓存区域。
  • len 是缓存区的最大尺寸。
  • flags 是 recv() 函数的一个标志,一般都为 0 (具体的其他数值和含义请参考 recv()的 man pages)。


当 套 接 字 已 经 打 开 但 尚 未 有 连 接 的 时 候 用setsockopt()系统调用在其上设定选项(options)。setsockopt() 调用设置选项而 getsockopt()从给定的套接字取得选项。

int getsockopt(int sockfd, int level, int name, char *value, int *optlen);
int setsockopt(int sockfd, int level, int name, char *value, int *optlen);

  • sockfd 必须是一个已打开的套接字。
  • level 是函数所使用的协议标准(protocol level) (TCP/IP 协议使用 IPPROTO_TCP,套接字标准的选项实用 SOL_SOCKET)。
  • name 选项在套接字说明书中(man page)有详细说明。
  • value 指向为 getsockopt()函数所获取的值,setsockopt()函数所设置的值的地址。
  • optlen 指针指向一个整数,该整数包含参数以字节计算的长度

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

  • sockfd 是你想取得远程信息的那个套接字描述符。
  • addr 是一个指向 struct sockaddr (或是 struct sockaddr_in)的指针。
  • addrlen 是一个指向 int 的指针,应该赋于 sizeof(struct sockaddr)的大小。

int gethostname(char *hostname, size_t size);

  • hostname 是一个指向字符数组的指针,当函数返回的时候,它里面的数据就是本地的主机的名字.
  • size 是 hostname 指向的数组的长度.

#include <netdb.h>
struct hostent *gethostbyname(const char *name);

正如你所看见的,它返回了一个指向 struct hostent 的指针.Struct hostent 是这样定义的:
struct hostent {
    char *h_name;
    char **h_aliases;
    int h_addrtype;
    int h_length;
    char **h_addr_list;
};
#define h_addr h_addr_list[0]
下面是上面各个域代表含义的解释:

  • h_name 是这个主机的正式名称。
  • h_aliases 是一个以 NULL(空字符)结尾的数组,里面存储了主机的备用名称。
  • h_addrtype 是返回地址的类型,一般来说是“AF_INET”。
  • h_length 是地址的字节长度。
  • h_addr_list 是一个以 0 结尾的数组,存储了主机的网络地址。


#include <fcntl.h>
int fcntl (int fd, int cmd, long arg);


三. select I/O多路复用

int select(int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

  • numfds 是 readfds ,writefds,exceptfds 中 fd 集合中文件描述符中最大的数字加上1。
  • readfds 中的 fd 集合将由 select 来监视是否可以读取。
  • writefds 中的 fds 集合将由 select 来监视是否可以写入。
  • exceptfds 中的 fds 集合将由 select 来监视是否有例外发生。


  1. FD_ZERO(fd_set *set)将一个文件描述符集合清零
  2. FD_SET(int fd, fd_set *set)将文件描述符 fd 加入集合 set 中。
  3. FD_CLR(int fd, fd_set *set)将文件描述符 fd 从集合 set 中删除.
  4. FD_ISSET(int fd, fd_set *set)测试文件描述符 fd 是否存在于文件描述符 set 中

http://blog.csdn.net/xiajun07061225/article/details/9250579

四. epoll I/O多路复用

        epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用

1. int epoll_create(int size)

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    epoll事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册   要监听的事件类型

  第一个参数是epoll_create()的返回值。

  第二个参数表示动作,用三个宏来表示:

  • EPOLL_CTL_ADD:注册新的fdepfd中;
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL:从epfd中删除一个fd

   第三个参数是需要监听的fd

   第四个参数是告诉内核需要监听什么事

  1. //保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)  
  2.   
  3. typedef union epoll_data {  
  4.     void *ptr;  
  5.     int fd;  
  6.     __uint32_t u32;  
  7.     __uint64_t u64;  
  8. } epoll_data_t;  
  9.  //感兴趣的事件和被触发的事件  
  10. struct epoll_event {  
  11.     __uint32_t events; /* Epoll events */  
  12.     epoll_data_t data; /* User data variable */  
  13. }; 

       events可以是以下几个宏的集合

  • EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

     events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存



epoll 工作原理
             epoll 同样只告知那些就绪的文件描述符,而且当我们调用 epoll_wait() 获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去 epoll 指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射( mmap )技术 ,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
    epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

LT(level triggered)是epoll缺省的工作方式,并且同时支持blockno-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表
ET (edge-triggered)是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。


       


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值