unix socket

socket和电话筒

Socket Domain Families:
AF_INET
AF_UNIX

Two Types of Internet Socket:
Datagram Socket : UDP protocol
Stream Socket : TCP protocol

套接字地址结构 :
struct sockaddr {
   unsigned short sa_family;  /* address族, AF_xxx */
   char sa_data[14];         /* 14 bytes的协议地址 ip和端口信息 */
};


Internet的套接字地址:
struct sockaddr_in {
   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 sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(79);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");


一个指向struct sockaddr_in 的指针可以声明指向一个sturct sockaddr 的结构。

htons()——Host to Network Short
htonl()——Host to Network Long
ntohs()——Network to Host Short
ntohl()——Network to Host Long


TCP client:

socket -> connect -> read/write -> close

  int socket(int domain, int type, int protocol); 
  int sockfd = socket(PF_INET, SOCK_STREAM, 0);

  int connect(int sockfd, struct sockaddr *server_addr, socklen_t len);
  connect(sockfd, &server, sizeof(server);

  int read(int sockfd, void *buffer, size_t buffer_size);
  read(newfd, buffer, sizeof(buffer));

  int write(sint sockfd, void *buffer, size_t buffer_size);
  write(newfd, buffer, sizeof(buffer));


TCP server:

socket -> bind -> listen -> accept -> read/write -> close

  int socket(int domain, int type, int protocol); 
  int sockfd = socket(PF_INET, SOCK_STREAM, 0);  // 0 表示取默认项

  int bind(int sockfd, struct sockaddr *server_addr, sockelen_t len);
  bind(sockfd, &server, sizeof(server));

  int listen(int sockfd, int backlog); // backlog 有多少个连接请求
  listen(sockfd, 1024); 

  int accept(int sockfd, struct sockaddr *incoming_address, socklen_t len);
  int newfd = accept(sockfd, &client, sizeof(client);   /* 阻塞 */

  int read(int sockfd, void *buffer, size_t buffer_size);
  read(newfd, buffer, sizeof(buffer));

  int write(sint sockfd, void *buffer, size_t buffer_size);
  write(newfd, buffer, sizeof(buffer));


UDP client:
  socket -> bind -> sendto -> recvfrom

UDP server
  socket -> bind -> recvfrom -> sendto



reading from and write to stream socket:

low level IO : read() / write()
higher level IO :
    int recv(int socket, char *buf, int len, int flags);
        blocks on read
        return 0 when other connection has terminated
    int send(int socket, char *buf, int len, int flags);
        return the number of bytes actually sent

    flags 可能取值:
    MSG_DONTROUNT   : don't route out of localnet
    MSG_OOB         : out of band data
    MSG_PEEK        : examin, but don't remove from stream


sendto and recvfrom to datagram socket

sendto(socket, buffer, buflen, flags, to_addr, tolen);
recvfrom(socket, buffer, buflen, flags, from_addr, fromlen);


Closing a socket session:
int close(int socket);  // close read/write IO, close socket file descriptor
int shutdown(int socketfd, int how);
   how -> 0  no more receives allowed
       -> 1  no more sends are allowed
       -> 2 disable both receives and sends ( but doesn't close the socket)


1 阻塞I/O模型: 应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好。 如果数据没有准备好,一直等待….数据准备好了,从内核拷贝到用户空间,IO函数返回成功指示。

  recv()、recvfrom()函数。以阻塞套接字为参数调用该函数接收数据。如果此时套接字缓冲区内没有数据可读,则调用线程在数据到来前一直睡眠。
 
  send()、sendto()函数。以阻塞套接字为参数调用该函数发送数据。如果套接字缓冲区没有可用空间,线程会一直睡眠,直到有空间。
 
  accept()函数。以阻塞套接字为参数调用该函数,等待接受对方的连接请求。如果此时没有连接请求,线程就会进入睡眠状态。

   connect()函数。对于TCP连接,客户端以阻塞套接字为参数,调用该函数向服务器发起连接。该函数在收到服务器的应答前,不会返回。这意味着TCP连接总会等待至少到服务器的一次往返时间。

阻塞模式的套接字,容易实现。当希望能够立即发送和接收数据,且处理的套接字数量比较少的情况下,使用阻塞模式来开发网络程序比较合适。

2 非阻塞IO模型
  把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。
  
当使用socket()函数创建套接字时,默认都是阻塞的。在创建套接字之后,通过调用fcntl()将该套接字设置为非阻塞模式。

套接字设置为非阻塞模式后,在调用socket函数时,调用函数会立即返回。大多数情况下,这些函数调用都会调用“失败”,并返回 EWOULDBLOCK 错误代号。


在While循环体内不断地调用recv()函数,以读入期望的字节数, 非常浪费系统资源.

使用MSG_PEEK标志调用recv()函数查看缓冲区中是否有数据可读。同样,这种方法也不好。因为该做法对系统造成的开销是很大的,并且应用程序至少要调用recv()函数两次,才能实际地读入数据。


3 IO复用模型
I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有


select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。
单个进程可监视的fd数量被限制 32位机默认是1024个
对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低
需要维护一个用来存放大量fd的数据结构,用户空间和内核空间在传递该结构时复制开销大


poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。

它没有最大连接数的限制,原因是它是基于链表来存储的
1、大量的fd的数组被整体复制于用户态和内核地址空间之间
2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。


epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知

1、没有最大并发连接的限制,1G的内存上能监听约10万个端口;
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数。
3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。


一个完整的Socket描述:
{协议,本地地址,本地端口,远程地址,远程端口}

每一个Socket 有一个本地的唯一Socket 号,由操作系统分配。

套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)
及原始套接字。



TCP/IP 协议族:
1.控制数据的协议
TCP(传输控制协议Transmission Control Protocol)
UDP(使用者数据报协议User Datagram Protocol)

2.数据路由协议
IP(因特网协议Internet Protocol)
ICMP(因特网控制信息协议Internet Control Message Protocol)
RIP (路由信息协议Routing Information Protocol)
OSPF(Open Shortest Path First)一个用来决定路由的协议。
DNS(域名系统Domain Name System)
ARP(地址决定协议Address Resolution Protocol)确定网络上一台电脑的数字地址。
RARP(反向地址决定协议Reverse Address Resolution Protocol)

参考:
http://blog.csdn.net/hguisu/article/details/7453390

猜你喜欢

转载自catdoc.iteye.com/blog/2111790