OS | Socket

TCP

创建socket:

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

AF = Address Family
PF = Protocol Family

AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)

TCP: SOCK_STREAM

UDP: SOCK_DGRAM

RAW: SOCK_RAW (Provides raw network protocol access)

绑定:

1 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:
程序员不应操作sockaddr,sockaddr是给操作系统用的
程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。

一般的用法为:
程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数

 1 include <netinet/in.h>
 2
 3 struct sockaddr {
 4     unsigned short    sa_family;    // 2 bytes address family, AF_xxx
 5     char              sa_data[14];     // 14 bytes of protocol address
 6 };
 7
 8 // IPv4 AF_INET sockets:
 9
10 struct sockaddr_in {
11     short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6
12     unsigned short   sin_port;    // 2 bytes e.g. htons(3490)
13     struct in_addr   sin_addr;     // 4 bytes see struct in_addr, below
14     char             sin_zero[8];     // 8 bytes zero this if you want to
15 };
16
17 struct in_addr {
18     unsigned long s_addr;          // 4 bytes load with inet_pton()
19 };

htons 和htonl用来将主机字节顺序转换为网络字节顺序。(小端和大端)

监听:

1 int listen(int sockfd, int backlog);

backlog这个参数涉及到一些网络的细节。在进程正处理一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。

接收:

1 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

返回的是客户端的socket和地址;

发送数据:

1 ssize_t send(int sockfd, const void *buf, size_t len, int flags);

测试1

当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。
根据信号的默认处理规则SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出。若不想客户端退出可以把SIGPIPE设为SIG_IGN

如: signal(SIGPIPE,SIG_IGN);
这时SIGPIPE交给了系统处理。

设置之后可以看到错误是 error: Broken pipe。

当服务器进程终止时,无论应用程序是否显式关闭了socket, OS会负责在进程结束时关闭所有的文件描述符,对于socket,则会发送一个FIN包到对面。假如服务器进程是异常终止的,发送FIN包是OS代劳的,服务器进程已经不复存在,当服务器再次收到该socket的消息时,会回应RST(因为拥有该socket的进程已经终止)。客户端进程对收到RST的socket调用write时,操作系统会给客户端进程发送SIGPIPE,默认处理动作是终止进程。

测试2

每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的buffer以及此buffer的填充状态。接收缓冲区把数据缓存入内核,应用进程一直没有调用read进行读取的话,此数据会一直缓存在相应socket的接收缓冲区内。再啰嗦一点,不管进程是否读取socket,对端发来的数据都会经由内核接收并且缓存到socket的内核接收缓冲区之中。read所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,仅此而已。进程调用send发送的数据的时候,最简单情况(也是一般情况),将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回。换句话说,send返回之时,数据不一定会发送到对端去(和write写文件有点类似),send仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中。

我用服务器每隔10秒接收一次数据,客户端每隔1秒发送一次数据,服务器读出的就是客户端10次发送的总数据。

测试3

对于blocking的write有个特例:当write正阻塞等待时对面关闭了socket,则write则会立即将剩余缓冲区填满并返回所写的字节数,再次调用则write失败(connection reset by peer)。

我直接杀掉服务器进程,客户端再调用send时,出现error: Connection reset by peer。

测试4

客户端持续发送,服务器端能够一直确保有序和不丢包。

建立一个socket,通过getsockopt获取缓冲区的值如下:
发送缓冲区大小:SNDBufSize = 16384
接收缓冲区大小:RCVBufSize = 87380

设置小于2240的值,缓冲区大小为2240.大于2240的值,缓冲区大小为设置值的2倍。

不知道为什么,发送一定数据量之后, 客户端陷入等待,但是此时发送量并不等于缓冲区大小。

测试5

设置了listen的backlog参数为2,同时开启了5个client进程,都能够连接成功,和理解的不同,不知道为什么?

异步socket

linux下有五种I/O模型:

  1. 阻塞I/O(blocking I/O)
  2. 非阻塞I/O (nonblocking I/O)
  3. I/O复用(select 和poll) (I/O multiplexing)
  4. 信号驱动I/O (signal driven I/O (SIGIO))
  5. 异步I/O (asynchronous I/O (the POSIX aio_functions))

每次都要把所有的socket加到read flags。 不设置的话,当没有读事件时,select会把read flags该位清空,之后没有socket可检查,所以会一直超时。

用FD_ISSET判断可以判断server socket或者client socket是否可读。server socket可读,证明有新连接。client socket可读证明有新数据。

select也会改变等待时间,所以调用select之前都要重新初始化timeval。

1 int select(int nfds, fd_set *readfds, fd_set *writefds,
2                   fd_set *exceptfds, struct timeval *timeout);
3
4 void FD_CLR(int fd, fd_set *set);
5 int  FD_ISSET(int fd, fd_set *set);
6 void FD_SET(int fd, fd_set *set);
7 void FD_ZERO(fd_set *set);

例子

阻塞的比较简单,udp也比较简单。所以这里展示的是非阻塞的。

client

 1 #include <stdio.h>
 2 #include <sys/types.h> //socket
 3 #include <sys/socket.h> //socket
 4 #include <string.h> //strerror
 5 #include <errno.h> //errno
 6 #include <netinet/in.h> // sockaddr_in
 7 #include <arpa/inet.h> //inet_ntoa
 8 #include <unistd.h>
 9 #include <signal.h>
10
11 enum {
12     SERVER_PORT = 8888,
13     BUFFER_SIZE = 1024,
14 };
15
16 int main() {
17     int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
18     if (clientSocket < 0) {
19         printf("error: %s\n", strerror(errno));
20         return -1;
21     }
22     sockaddr_in serverAddr;
23     bzero(&serverAddr, sizeof(serverAddr));
24     serverAddr.sin_family = AF_INET; //ipv4
25     serverAddr.sin_port = htons(SERVER_PORT);
26     serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // server ip, any ips
27
28     if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
29         printf("error: %s\n", strerror(errno));
30         close(clientSocket);
31         return -1;
32     }
33
34     char buffer[BUFFER_SIZE + 1];
35
36     sprintf(buffer, "hello0");
37     signal(SIGPIPE,SIG_IGN);
38     int osBufferSize;
39     int osBufferSizeBytes = sizeof(osBufferSize);
40     if (getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF,
41                 (void *)&osBufferSize,(socklen_t*)&osBufferSizeBytes) < 0){
42         printf("error: %s\n", strerror(errno));
43         return -1;
44     }
45     printf("the sender buffer len: %d\n", osBufferSize);
46
47     int i = 1;
48     int sendBytes = 0;
49     while (send(clientSocket, buffer, strlen(buffer), 0) >= 0) {
50         sendBytes += strlen(buffer);
51         sprintf(buffer, "h%0d", i++);
52         printf("send: %d\n", sendBytes);
53         //sleep(1);
54     }
55     printf("error: %s\n", strerror(errno));
56     close(clientSocket);
57     return 0;
58 }

server

  1 #include <stdio.h>
  2 #include <sys/types.h> //socket
  3 #include <sys/socket.h> //socket
  4 #include <string.h> //strerror
  5 #include <errno.h> //errno
  6 #include <netinet/in.h> // sockaddr_in
  7 #include <arpa/inet.h> //inet_ntoa
  8 #include <unistd.h>
  9 #include <fcntl.h>
 10
 11 enum {
 12     SERVER_PORT = 8888,
 13     BUFFER_SIZE = 32,
 14 };
 15
 16 bool setSocketRcvBufferSize(int sock, int osBufferSize) {
 17     int osBufferSizeBytes = sizeof(osBufferSize);
 18     if (setsockopt(sock, SOL_SOCKET, /*SO_SNDBUF*/SO_RCVBUF,
 19                 (void *)&osBufferSize, osBufferSizeBytes) < 0){
 20         printf("error: %s\n", strerror(errno));
 21         return false;
 22     }
 23     if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF,
 24                 (void *)&osBufferSize,(socklen_t*)&osBufferSizeBytes) < 0){
 25         printf("error: %s\n", strerror(errno));
 26         return false;
 27     }
 28     printf("the recevice buffer len: %d\n", osBufferSize);
 29     return true;
 30 }
 31
 32 void setNonblocking(int sock) {
 33     int ret = fcntl(sock,F_GETFL, 0);              // Get socket flags
 34     fcntl(sock,F_SETFL, ret | O_NONBLOCK);   // Add non-blocking flag
 35 }
 36
 37 int main() {
 38     int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
 39     if (serverSocket < 0) {
 40         printf("error: %s\n", strerror(errno));
 41         return -1;
 42     }
 43
 44     setNonblocking(serverSocket);
 45
 46     sockaddr_in serverAddr;
 47     bzero(&serverAddr, sizeof(serverAddr));
 48     serverAddr.sin_family = AF_INET; //ipv4
 49     serverAddr.sin_port = htons(SERVER_PORT);
 50     serverAddr.sin_addr.s_addr = INADDR_ANY; // server ip, any ips
 51
 52     if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
 53         printf("error: %s\n", strerror(errno));
 54         close(serverSocket);
 55         return -1;
 56     }
 57
 58     if (listen(serverSocket, 1) < 0) { // almost two connections at the same time
 59         printf("error: %s\n", strerror(errno));
 60         close(serverSocket);
 61         return -1;
 62     }
 63
 64     setSocketRcvBufferSize(serverSocket, 4096);
 65
 66     char buffer[BUFFER_SIZE + 1];
 67     int recvLen = 0;
 68     struct timeval waitt;
 69     fd_set readFlags;
 70     int clientSocket;
 71     int maxSocket = serverSocket;
 72     while (true) {
 73         FD_ZERO(&readFlags);
 74         FD_SET(serverSocket, &readFlags);
 75         if (clientSocket > 0) FD_SET(clientSocket, &readFlags);
 76         waitt.tv_sec = 2;
 77         waitt.tv_usec = 0;
 78         int stat = select(maxSocket + 1, &readFlags, NULL, NULL, &waitt); // waitt will be change to zero
 79         if (stat < 0) {
 80             printf("error: %s\n", strerror(errno));
 81             continue;
 82         } else if (stat == 0) {
 83             printf("select timeout\n");
 84             continue;
 85         }
 86
 87         if (FD_ISSET(serverSocket, &readFlags)) {
 88             sockaddr_in clientAddr;
 89             int clientAddrSize = sizeof(clientAddr);
 90             clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrSize);
 91             if (clientSocket > maxSocket) maxSocket = clientSocket;
 92             printf("client: %s:%d\n", inet_ntoa(clientAddr.sin_addr),
 93                     ntohs(clientAddr.sin_port));
 94         }
 95
 96         if (clientSocket < 0) continue;
 97
 98         if (FD_ISSET(clientSocket, &readFlags)) {
 99             FD_CLR(clientSocket, &readFlags);
100             bzero(buffer, BUFFER_SIZE);
101             recvLen = recv(clientSocket, buffer, BUFFER_SIZE, 0);
102             buffer[recvLen] = ‘\0‘;
103             printf("recv: %s\n", buffer);
104         }
105     }
106     close(clientSocket);
107     close(serverSocket);
108     return 0;
109 }

OS | Socket,布布扣,bubuko.com

时间: 2024-10-24 02:16:46

OS | Socket的相关文章

读懂Java中的Socket编程(转)

Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的Socket编程,并且如何写一个客户端/服务器程序. 餐前甜点 Unix的输入输出(IO)系统遵循Open-Read-Write-Close这样的操作范本.当一个用户进程进行IO操作之前,它需要调用Open来指定并获取待操作文件或设备读取或写入的权限.一旦IO操作对象被打开,那么这个用户进程可以对这个

(转)Java Socket编程

原文出自:http://www.cnblogs.com/rocomp/p/4790340.html Socket是网络驱动层提供给应用程序编程接口和一种机制.可以把Socket比喻成一个港口码头,应用程序只要把货物放到港口码头上,就算完成了货物的运送.对于接收方应用程序也要创建一个港口码头,只需等待货物到达码头后将货物取走. InetAddress          InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址.           该类没有构造方法       

Socket编程(三)★

★Sockets 使用TCP协议实现网络通信的Socket相关类(重点) TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议 服务器端的ServerSocket类        客户端的Socket类 ①.服务器端通信流程                                                           ②. 客户端通信流程    1. 创建ServerSocket对象,绑定该程

通过socket实现http通讯代码理解

1.首先构造http协议报头: String dd = "GET http://www.baidu.com HTTP/1.1" + "\r\n" + "Host: www.baidu.com" + "\r\n" + "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0" + "\r\n"

------------------------------------网络编程(Socket)(TCP )

1.Java.net 包提供若干支持基于套接字的客户端/服务器通信的类. 2.java.net包中常有的类有Socket.ServerSocket.DatagramPacket.InetAddress.URL.URLConnection和URLEncoder等 3.为了监听客户端的连接请求,可以使用ServerSocket类.Socket类实现用于网络上进程间通信的套接字.DatagramSocket类使用UDP协议实现客户端和服务器套接字.DatagramPacket类使用DatagramSo

基于多线程的TCP socket通信经典案例

服务器端 package com.thinkvenus.study.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; /** *

ServerSocket和Socket

前言 用ServerSocket和Socket做了个Server.Client通信的demo,以及学习下在这个demo过程中用到java.net.java.io包下几个常用的类. Server import java.net.*;import java.io.*;public class HttpServer{         public static void main(String[] args){        new HttpServer().start();     }       

java Socket编程-基于TCP

package com.wzy.Test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; /** * * @author wzy *服务器线程

Java Socket编程----通信是这样炼成的

Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socket.像大家熟悉的QQ.MSN都使用了Socket相关的技术.下面就让我们一起揭开Socket的神秘面纱. Socket编程 一.网络基础知识(参考计算机网络)            关于计算机网络部分可以参考相关博客:           <TCP/IP协议栈及OSI参考模型详解> http://wangdy.blog.51cto.com/3845563/