OS | Socket
TCP
创建socket:
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)
绑定:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:
程序员不应操作sockaddr,sockaddr是给操作系统用的
程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。
一般的用法为:
程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数
include <netinet/in.h> struct sockaddr {
unsigned short sa_family; // 2 bytes address family, AF_xxx
char sa_data[]; // 14 bytes of protocol address
}; // IPv4 AF_INET sockets: struct sockaddr_in {
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 bytes e.g. htons(3490)
struct in_addr sin_addr; // 4 bytes see struct in_addr, below
char sin_zero[]; // 8 bytes zero this if you want to
}; struct in_addr {
unsigned long s_addr; // 4 bytes load with inet_pton()
};
htons 和htonl用来将主机字节顺序转换为网络字节顺序。(小端和大端)
监听:
int listen(int sockfd, int backlog);
backlog这个参数涉及到一些网络的细节。在进程正处理一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。
接收:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回的是客户端的socket和地址;
发送数据:
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模型:
- 阻塞I/O(blocking I/O)
- 非阻塞I/O (nonblocking I/O)
- I/O复用(select 和poll) (I/O multiplexing)
- 信号驱动I/O (signal driven I/O (SIGIO))
- 异步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。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
例子
阻塞的比较简单,udp也比较简单。所以这里展示的是非阻塞的。
client
#include <stdio.h>
#include <sys/types.h> //socket
#include <sys/socket.h> //socket
#include <string.h> //strerror
#include <errno.h> //errno
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> //inet_ntoa
#include <unistd.h>
#include <signal.h> enum {
SERVER_PORT = ,
BUFFER_SIZE = ,
}; int main() {
int clientSocket = socket(AF_INET, SOCK_STREAM, );
if (clientSocket < ) {
printf("error: %s\n", strerror(errno));
return -;
}
sockaddr_in serverAddr;
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET; //ipv4
serverAddr.sin_port = htons(SERVER_PORT);
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // server ip, any ips if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < ) {
printf("error: %s\n", strerror(errno));
close(clientSocket);
return -;
} char buffer[BUFFER_SIZE + ]; sprintf(buffer, "hello0");
signal(SIGPIPE,SIG_IGN);
int osBufferSize;
int osBufferSizeBytes = sizeof(osBufferSize);
if (getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF,
(void *)&osBufferSize,(socklen_t*)&osBufferSizeBytes) < ){
printf("error: %s\n", strerror(errno));
return -;
}
printf("the sender buffer len: %d\n", osBufferSize); int i = ;
int sendBytes = ;
while (send(clientSocket, buffer, strlen(buffer), ) >= ) {
sendBytes += strlen(buffer);
sprintf(buffer, "h%0d", i++);
printf("send: %d\n", sendBytes);
//sleep(1);
}
printf("error: %s\n", strerror(errno));
close(clientSocket);
return ;
}
server
#include <stdio.h>
#include <sys/types.h> //socket
#include <sys/socket.h> //socket
#include <string.h> //strerror
#include <errno.h> //errno
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> //inet_ntoa
#include <unistd.h>
#include <fcntl.h> enum {
SERVER_PORT = ,
BUFFER_SIZE = ,
}; bool setSocketRcvBufferSize(int sock, int osBufferSize) {
int osBufferSizeBytes = sizeof(osBufferSize);
if (setsockopt(sock, SOL_SOCKET, /*SO_SNDBUF*/SO_RCVBUF,
(void *)&osBufferSize, osBufferSizeBytes) < ){
printf("error: %s\n", strerror(errno));
return false;
}
if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF,
(void *)&osBufferSize,(socklen_t*)&osBufferSizeBytes) < ){
printf("error: %s\n", strerror(errno));
return false;
}
printf("the recevice buffer len: %d\n", osBufferSize);
return true;
} void setNonblocking(int sock) {
int ret = fcntl(sock,F_GETFL, ); // Get socket flags
fcntl(sock,F_SETFL, ret | O_NONBLOCK); // Add non-blocking flag
} int main() {
int serverSocket = socket(AF_INET, SOCK_STREAM, );
if (serverSocket < ) {
printf("error: %s\n", strerror(errno));
return -;
} setNonblocking(serverSocket); sockaddr_in serverAddr;
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET; //ipv4
serverAddr.sin_port = htons(SERVER_PORT);
serverAddr.sin_addr.s_addr = INADDR_ANY; // server ip, any ips if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < ) {
printf("error: %s\n", strerror(errno));
close(serverSocket);
return -;
} if (listen(serverSocket, ) < ) { // almost two connections at the same time
printf("error: %s\n", strerror(errno));
close(serverSocket);
return -;
} setSocketRcvBufferSize(serverSocket, ); char buffer[BUFFER_SIZE + ];
int recvLen = ;
struct timeval waitt;
fd_set readFlags;
int clientSocket;
int maxSocket = serverSocket;
while (true) {
FD_ZERO(&readFlags);
FD_SET(serverSocket, &readFlags);
if (clientSocket > ) FD_SET(clientSocket, &readFlags);
waitt.tv_sec = ;
waitt.tv_usec = ;
int stat = select(maxSocket + , &readFlags, NULL, NULL, &waitt); // waitt will be change to zero
if (stat < ) {
printf("error: %s\n", strerror(errno));
continue;
} else if (stat == ) {
printf("select timeout\n");
continue;
} if (FD_ISSET(serverSocket, &readFlags)) {
sockaddr_in clientAddr;
int clientAddrSize = sizeof(clientAddr);
clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrSize);
if (clientSocket > maxSocket) maxSocket = clientSocket;
printf("client: %s:%d\n", inet_ntoa(clientAddr.sin_addr),
ntohs(clientAddr.sin_port));
} if (clientSocket < ) continue; if (FD_ISSET(clientSocket, &readFlags)) {
FD_CLR(clientSocket, &readFlags);
bzero(buffer, BUFFER_SIZE);
recvLen = recv(clientSocket, buffer, BUFFER_SIZE, );
buffer[recvLen] = '\0';
printf("recv: %s\n", buffer);
}
}
close(clientSocket);
close(serverSocket);
return ;
}
OS | Socket的更多相关文章
- Socket聊天程序——Common
写在前面: 上一篇记录了Socket聊天程序的客户端设计,为了记录的完整性,这里还是将Socket聊天的最后一个模块--Common模块记录一下.Common的设计如下: 功能说明: Common模块 ...
- socket Bio demo
最近在做socket通信,最开始是基于Bio开发(其实开发的时候也不知道这种是基于BIO).但是问题来了,客户端发的报文,服务端接收会少,为了解决问题,只能恶补一下相关知识. 服务端: import ...
- Socket通信类
package com.imooc; import java.io.BufferedReader; import java.io.IOException; import java.io.InputSt ...
- 基于TCP协议的socket通信
一.服务器端 1.创建serverSocket,即服务器端的socket,绑定指定的端口,并侦听此端口 ServerSocket server = new ServerSocket(8888); 2. ...
- java Socket编程-基于TCP
package com.wzy.Test; import java.io.BufferedReader; import java.io.IOException; import java.io.Inpu ...
- Java 网络编程之 Socket
========================UDP============================= UDP---用户数据报协议,是一个简单的面向数据报的运输层协议. UDP不提供可靠性, ...
- 多线程完成socket
//服务器端代码 public class Service { //服务器 public static void main(String[] args) { ServerSocket serverSo ...
- JAVA Socket 编程学习笔记(一)
1. Socket 通信简介及模型 Java Socket 可实现客户端--服务器间的双向实时通信.java.net包中定义的两个类socket和ServerSocket,分别用来实现双向连接的cli ...
- Java Socket编程
Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socket.像大家熟悉的QQ.MSN都使用了Socket相关的技术. ...
随机推荐
- 通过session模拟登陆
import requests # 这个练习对比的是上一个登陆练习,这个是不用自己传入cookie参数,而是利用session方法登陆 # 实例化一个session session = request ...
- redis集群监控之Redis-monitor部
为了对以后有可能面临的redis集群监控做准备,这两天在准备这方面的事情,现在将其中的过程记录一下. 首先是“Ronney-Hua”的这篇文章对三中开源监控软件做了对比 文章地址:https://bl ...
- 天问之Linux内核中的不明白的地方
1. Linux 0.11\linux\kernel\exit.c 文件中, 无论是send_sig()函数还是kill_session()函数中,凡是涉及到发送信号的地方,都是直接 (*p)- ...
- NSNotificationCenter的用法
作用:NSNotificationCenter是专门供程序中不同类间的消息通信而设置的. 注册通知:即要在什么地方接受消息 [[NSNotificationCenter defaultCenter] ...
- Leetcode 145. 二叉树的后序遍历
题目链接 https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/ 题目描述 给定一个二叉树,返回它的 ...
- 给vagrant中的precise64升级VBoxGuestAdditions
位置:/usr/share/virtualbox/VBoxGuestAdditions.iso 在host(ubuntu 12.04 64)中: 查看虚拟机的名字:jb@H38:~/vm/vagran ...
- python基础学习笔记——类的成员
一. 细分类的组成成员 之前咱们讲过类大致分两块区域,如下图所示: 每个区域详细划分又可以分为: class A: company_name = '老男孩教育' # 静态变量(静态字段) __ipho ...
- luogu1578 奶牛浴场 枚举点最大子矩阵
建议看看王知昆dalao的论文,讲得很好 #include <algorithm> #include <iostream> #include <cstring> # ...
- 修改DB-LINK连接数方法
原因分析有可能是DB-LINK连接数的限制,请做如下修改验证: 以oracle用户登录数据库节点. 连接数据库. $ sqlplus "/as sysdba"修改DataBase ...
- cinema 4d 包括宝典 --- 改线 循环边 建模布线原则
cinema 4d 一.视图控制与物体控制 1.摇移 alt+鼠标左键 转圈看物体 改变角度 2.平移 alt +鼠标中键 不改变角度 移动 3.推拉 alt+鼠标右键 ...