1.socket的通信流程

这是socket的通信流程图。

在linux系统中,一切皆文件,socket也被看作是文件。

所以socket的通信可以看作是往socket文件中写入数据和读取数据的过程。

1.1 服务端流程

(1)创建socket文件,使用socket 函数。

(2)将一个ip和端口绑定到这个socket文件,使用bind 函数。

(3)让这个socket处于监听状态,使用listen函数。

(4)准备接收客户端请求的到来。如果不来,就一直在这里等待。使用accept函数。

(5)终于收到请求了,然后处理请求,可以读取socket中的数据(使用read函数),也可以往socket中写入数据(使用write函数)。

(6)客户端发起关闭连接请求,通过read函数读取并识别。

(7)服务器收到请求后关闭自己的socket,使用close函数。

这就是服务器端的流程。

1.2 客户端流程

客户端比服务端更简单,它没有监听的这个部分,多了一个发起连接请求的部分。

(1)创建socket文件,使用socket 函数。

(2)和服务端建立连接,著名的TCP3次握手就是这一步完成的,使用connect函数。

(3)连接建立好之后,就可以往socket中读写数据了。使用read、write函数

(4)通信完成后,发起关闭socket的请求,使用close函数。

2.函数说明

可以直接在linux系统中查询函数的说明文档。用man 函数名。比如查询socket函数:

$ man socket

2.1 socket函数

用途:

socket()打开一个网络通讯端口,可以理解为创建一个socket文件。创建成功后可以像普通文件一样对它进行读写操作,也就是在网络上进行收发数据。接收就是读,发送就是写。

返回值:

成功:返回这个socket文件的文件描述符。

失败:返回-1。

需要包含的头文件:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

函数原型:

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

参数说明:

(1)domain:

用于指明通信使用的协议族(最常用的是IPV4网络协议)。

参数有以下一些常用的取值,这些宏定义在<sys/socket.h>中。

1)AF_INET ——这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址。

2)AF_INET6—— 与上面类似,不过是来用IPv6的地址。

3)AF_UNIX——本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台机器上的时候使用。

(2)type:

用于指明communication semantics(传输语义),有以下一些取值:

1)SOCK_STREAM——这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。

2)SOCK_DGRAM——这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

3)SOCK_SEQPACKET——该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。

4)SOCK_RAW——socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)

5)SOCK_RDM——这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

(3)protocol:

​ 一般情况下传0,表示使用默认协议。

2.2 bind函数

用途:

将网络地址和端口号和socket绑定在一起。

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序知道服务器程序的地址和端口号后就可以向服务器发起连接。

返回值:

成功:返回0。

失败:返回-1, 设置errno。

需要包含的头文件:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

函数原型:

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

参数说明:

(1)sockfd:

需要用来绑定地址的socket文件描述符。

(2)add:

构造出IP地址加端口号。

这里详细说明一下sockaddr数据结构类型。

struct sockaddr {sa_family_t sa_family;      /* address family, AF_xxx */char sa_data[14];           /* 14 bytes of protocol address */
};
struct sockaddr_in {sa_family_t   sin_family; /* address family: AF_INET */in_port_t    sin_port;  /* port in network byte order */struct in_addr sin_addr;  /* internet address */};
 /* Internet address. */
struct in_addr {uint32_t    s_addr;   /* address in network byte order */
};

IPv4的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址。

很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些像bind 、accept函数的参数都用struct sockaddr *类型表示,在传递参数之前要强制类型转换一下,例如:

struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));

(3)addrlen:

sizeof(addr)长度。

由于struct sockaddr *是一个通用指针类型(void*),addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同(比如Ipv4是32位ip地址,ipv6是128位地址),所以需要第三个参数addrlen指定结构体的长度。

2.3 listen函数

用途:

标记对应的socket处于监听状态,典型的服务器程序可以同时服务于多个客户端,这里用backlog指明最多允许有多少个客户端处于连接待状态,如果接收到更多的连接请求就忽略。

返回值:

成功:返回0。

失败:返回-1,。

需要包含的头文件:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

函数原型:

int listen(int sockfd, int backlog);

参数说明:

(1)sockfd:

socket文件描述符

(2)backlog:

排队等待建立3次握手队列的长度。超过这个长度后的连接请求就被忽略。

这个长度还收到系统默认backlog的影响,如果大于默认长度,还是以默认长度为准。

默认长度的查询方法:

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

默认长度的修改方法:

vim /etc/sysctl.conf

最后添加
net.core.somaxconn = 2048
net.ipv4.tcp_max_syn_backlog = 2048

保存,然后执行

sysctl -p

从1024修改成2048:

2.4 accept函数

用途:

用于接收客户端请求的连接。

三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。

返回值:

成功:返回一个新的socket文件描述符,用于和客户端通信。这个socket在此次通信结束后就关闭,而监听的socket还一直处于开启中。

失败:返回-1,设置errno。

需要包含的头文件:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

函数原型:

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

参数说明:

(1)sockfd:

socket文件描述符。

(2)addr:

是一个传出参数,返回连接的客户端地址信息,含IP地址和端口号。如果给addr参数传NULL,表示不关心客户端的地址。

(3)addrlen:

是一个传入+传出参数,传入值是sizeof(addr)大小(是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题),函数返回时返回真正接收到地址结构体的大小。

2.5 connect函数

用途:

客户端发起请求,连接对应的服务器。

返回值:

成功:返回0。

失败:返回-1,设置errno。

需要包含的头文件:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

函数原型:

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

参数说明:

(1)sockfd:

socket文件描述符

(2)addr:

传入参数,指定服务器端地址信息,含IP地址和端口号。

注意:这里是虽然是客户端调用的函数,但是是服务器的地址信息,用于发起请求,连接对应的服务器。

(3)addrlen:

传入参数,传入sizeof(addr)大小。

2.6 read函数

用途:

从文件描述符fd指定的文件中读取count个bytes的字符到缓冲区buf中。

返回值:

成功:返回读取的字节数 。

失败:返回-1,设置errno。

需要包含的头文件:

#include <unistd.h>

函数原型:

ssize_t read(int fd, void *buf, size_t count);

参数说明:

(1)fd:

文件描述符。

(2)buf:

缓冲区的地址。

(3)count:

需要读取的字节数。

2.7 write函数

linux查询的时候因为有重名情况,用man 2 write即可。

用途:

将缓冲区buf中count个字节的字符写入到文件描述符fd指定的文件中。

返回值:

成功:返回写入的字节数 。

失败:返回-1,设置errno。

需要包含的头文件:

#include <unistd.h>

函数原型:

 ssize_t write(int fd, const void *buf, size_t count);

参数说明:

(1)fd:

文件描述符。

(2)buf:

缓冲区的地址。

(3)count:

需要写入的字节数。

2.8 close函数

用途:

关闭一个文件描述符指定的文件。

返回值:

成功:返回0 。

失败:返回-1,设置errno。

需要包含的头文件:

#include <unistd.h>

函数原型:

 int close(int fd);

参数说明:

(1)fd:

文件描述符。

3.网络字节序知识

3.1 大端和小端

大端字节序 - 低地址高字节,高地址低字节

小段字节序 - 低地址低字节,高地址高字节

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

不同计算机可能是大端或小端模式。

3.1 网络字节序转换

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

h表示host,n表示network,l表示32位长整数,s表示16位短整数。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回。

如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

3.2 ip地址转换

头文件

#include <arpa/inet.h>

(1)inet_pton

将 IPv4 或者 IPv6地址从本机字节序的文本格式转换为网络字节序的二进制。

int inet_pton(int af, const char *src, void *dst);

成功: 返回 1。

失败:返回0(src地址和af的格式不匹配)

​ 返回-1(af不是有效的地址族)

(2)inet_ntop

将 IPv4 或者 IPv6地址从网络字节序的二进制转换为本机字节序的文本格式。

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

成功:返回指向dst的非空指针。

失败:返回NULL。

af 取值可选为 AF_INET 和 AF_INET6 ,即和 ipv4 和ipv6对应。

inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr。

还有两个功能类似的函数inet_aton() and inet_addr(),它们只支持ipv4的地址,不支持ipv6地址。

4.socket通信的代码

4.1 实现的功能

客户端发送一段字符串,服务端返回“hello”+收到的字符串。然后关闭连接。

下面代码就是:

客户端:jingjing

服务端:hello jingjing

4.2 服务端代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>int main()
{//1.创建一个socket文件,也就是打开一个网络通讯端口,类型是IPV4(AF_INET)+TCP(SOCK_STREAM)int serv_sock = socket(AF_INET, SOCK_STREAM,0);//2.绑定服务器ip和端口到这个socketstruct sockaddr_in serv_addr;//这里因为是ipv4,使用的结构体是ipv4的地址类型sockaddr_inmemset(&serv_addr, 0, sizeof(serv_addr));//先清空一下初始的值,写上地址和端口号,可以用bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//本机ip环回地址,这里还可以使用inet_pton函数进行地址转换serv_addr.sin_port = htons(8899);//随意选了一个端口8899bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//3.将socket设置为监听状态listen(serv_sock,128);//设置最大连接数为128//4.准备接收客户端的请求连接,这里的步骤可以重复进行,接收多个客户端的请求while(1){//接收客户端的请求连接后,返回一个新的socket(clnt_sock)用于和对应的客户端进行通信struct sockaddr_in clnt_addr;//作为一个传出参数socklen_t clnt_addr_size = sizeof(clnt_addr);//作为一个传入+传出参数int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);//5.读取客户端发送来的数据char recv_buf[256];char send_buf[512]="hello ";int len = read(clnt_sock,recv_buf,sizeof(recv_buf)-1);recv_buf[len] = '\0';//字符串以“\0”结尾//6.打印出客户端发来的消息printf("客户端发来的:%s\n",recv_buf);//7.加上hello处理后返回给客户端strcpy(send_buf+strlen("hello "),recv_buf);//注意这里不能用sizeof,要用strlen,不然包含了‘\0’,后面的jingjing就打印不出来了。write(clnt_sock,send_buf,sizeof(send_buf));//8.关闭客户端连接close(clnt_sock);break;}//9.关闭服务端监听的socketclose(serv_sock);return 0;}

4.3 客户端代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>int main()
{//1.创建socket,用于和服务端通信int sock = socket(AF_INET, SOCK_STREAM, 0);//2.向服务端发起请求连接struct sockaddr_in serv_addr;//首先要指定一个服务端的ip地址+端口,表明是向哪个服务端发起请求memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//注意,这里是服务端的ip和端口serv_addr.sin_port = htons(8899);connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//3.向服务端发送消息char send_buf[256] = "jingjing";char recv_buf[512];write(sock,send_buf,sizeof(send_buf));//4.接收服务端发来的消息int len = read(sock,recv_buf,sizeof(recv_buf)-1);recv_buf[len] = '\0';printf("收到服务端的返回:%s\n",recv_buf);//5.关闭socketclose(sock);return 0;}

4.4 运行

在一个电脑上分别打开2个终端,一个代表服务端,一个代表客户端

(1)服务端

(2)客户端

4.5 注意事项

上面的代码中没有涉及函数返回出错的相关处理,实际应用中,调用每个函数都应该对返回值进行有效性的判断和出错处理。

linux下的socket编程相关推荐

  1. Linux下简单socket编程

    Linux下简单socket编程 socket的英文翻译是接口.插座的意思,很形象,就相当于将两个台电脑用一根线连起来,线的两头分别是插头,插在两台电脑上,借此实现通信. 两台电脑通信,实际上是这两台 ...

  2. Linux下的socket编程学习(TCP)

    1.什么是socket? socket的英文翻译就是接口,插座的意思,很形象,就相当于将2台电脑用一根线连起来,线的两头插在不同的电脑上,借此实现通讯的功能. 两台电脑通信,实际上是这两台电脑上的某个 ...

  3. Linux下网络socket编程——实现服务器(select)与多个客户端通信

    一.关于socket通信 服务器端工作流程: 调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定 调用listen()函数监听socket() 函数创建的 ...

  4. Linux下C++ Socket编程实例

    参考文章: https://www.cnblogs.com/wuyepeng/p/9737583.html

  5. Linux下TCP网络编程-创建服务器与客户端

    一.前言 互联网概念诞生于20世纪60年代末,从9几年中国接入互联网开始到现在,生活的每个角落都能看到网络的使用.现在物联网时代.共享经济的到来,生活中不仅仅电脑.手机可以接入网络,身边的各个设备也能 ...

  6. linux C语言 socket编程教程(附两个例子)(socket教程)

    文章目录 1.网络中进程之间如何通信? 2.什么是Socket? 3.socket的基本操作 3.1.socket()函数 3.2.bind()函数 网络字节序与主机字节序 3.3.listen(). ...

  7. Linux下高级C编程(学习总结)

    Linux下高级C编程 第一章 unix/linux系统的基本概念 第二章 unix/linux系统下的编程基础和开发方式 第三章 unix/linux系统下的内存管理 第四章 unix/linux系 ...

  8. c++多线程模式下的socket编程(线程池实现)

    socket 编程可以说是一个基本的技术掌握,而多个客户端向服务端发送请求又是一个非常常见的场景,因此多线程模式下的socket编程则显得尤为常见与重要. 本文主要利用线程池的技术,来实现多线程的模式 ...

  9. Linux下的C编程实战

    Linux下的C编程实战(一) ――开发平台搭建 1.引言 Linux操作系统在服务器领域的应用和普及已经有较长的历史,这源于它的开源特点以及其超越Windows的安全性和稳定性.而近年来, Linu ...

最新文章

  1. 零基础学习python_异常处理(32-33课)
  2. 高性能ASP.NET站点构建之简单的优化措施
  3. hdu 5615 Jam's math problem(判断是否能合并多项式)
  4. Oracle 原理: 11g的启动和关闭
  5. jdk LocalDateTime mybatis 空指针解决办法
  6. 神经网络optimizer的发展历史整理
  7. 幼小衔接语言教案上c册_关于幼小衔接,这里有你最想要的解答
  8. CISC与RISC的区别?
  9. 蓝桥杯真题训练 2019.2题
  10. 《游戏编程模式》一7.5 状态对象应该放在哪里呢
  11. TWaver初学实战——基于HTML5的交互式地铁图
  12. 【3】基于OpenCV—Python绘制图片三维空间显示图
  13. JAVA如何在LINUX里编程,如何使用加多宝(jdb)在linux下调试Java程序
  14. 港股常见的宽基指数:恒生指数、H股指数和香港中小指数
  15. QT开发应用程序(17)-- 读写XLS文件
  16. Android Studio 模拟器打不开解决方法
  17. layer打开弹窗时传递参数(content:)【layer弹窗插件】
  18. 3、传输介质——同轴电缆
  19. WEB攻防-通用漏洞SQL读写注入ACCESS偏移注入MYSQLMSSQLPostgreSQL
  20. LeetCodeNo.55 跳跃游戏

热门文章

  1. html dom.parent,HTML DOM offsetParent用法及代码示例
  2. html表单标签form怎样设置空隙,Smartform中表(table)的行间距设置
  3. 环小音三分钟讲解黑石塔英雄模式攻略
  4. CRM源码­|CRM系统源码开发
  5. 对预测分析中FIRST和FOLLOW集合的理解
  6. PEG2k-PLGA2k-Ce6/Pt(IV)-DI/PS, 聚乙二醇修饰聚合物/光敏剂
  7. BIM 在国内的前景怎么样?在国外发展到了什么程度?
  8. LSH(局部敏感度哈希)
  9. 关于Postman的报错问题
  10. 专业的HDR软件——Aurora HDR与Photomatix Pro