2021-04-25

记关于socket通信学习。

什么是socket

socket翻译为插座,叫套接字。一种计算机间的通信方式。将插头插入电网就能获得电力。socket连接电源(因特网)的插头。

在linux中作为一个文件。linux中每个文件都有一个id叫文件描述符。通过socket()函数创建一个网络连接,socket()的返回值就是文件描述符。有了文件描述符就可以使用普通的文件操作函数来传输数据。像read()读取数据等。socket()建立连接后文件操作即可。

所学套接字为——internet套接字

套接字类型

关于连接

所以socket都有物理连接——internet。对于互联网是由多个计算机加路由组成的,数据传输时要选择经过哪个路由来到达接收端。无连接的套接字不规定路线的选择,基于各个路由的情况不同到达时间也不同,也可能到不了——尽力而为。

而面向连接的套接字在通信前都会先确定一条路径,路径如果有情况会自己改变。通信完毕后断开连接销毁路径。这条路径也叫“虚电路”,接受一个包后返回一个包来确定到达。

简化互联网模型

1.流格式套接字(SOCK_STREAM)

面向连接套接字。可靠双向的准确无误发送数据。丢失或损坏会重新发送。       他像一个传送带,按着顺序发送数据。用ip协议为路由,使用了tcp协议。自身内部有一个缓冲区来接受数据,所以发收不一致。

2.数据报格式套接字(SOCK_DGRAM)

无连接的套接字。只传输不校验,快!但是丢失不负责。数据发送接收同步。像送快递,分开部分后由不同的人去送,无顺序。也用ip协议做路由,用udp协议。像qq聊天的即时传输

TCP/IP协议

由OSI模型——开放式系统互联,简化而成的网络模型。TCP/IP概念层分为网络接口(针对不同物理网络的连接形式协议,常用Ethernet,FDDI,ATM协议),网络层(负责数据的传输,路径及地址选择,常用IP,ARP地址解析协议等),传输层(确认数据传输及进行纠错处理,常用TCP,UDP协议),应用层(各种服务及应用程序通过这层连接网络,常用HTTP,FTP,SMTP协议)。四层模型对数据进行封装。socket位于传输层。

通信中确认身份

IP:网际协议地址。在ipv4中一个局域网有一个ip。一个计算机也可以有一个ip

MAC:局域网地址。一个局域网有一个ip后在局域网中通过每个计算机独有的MAC地址来确定时哪一台计算机

端口号:让计算机接受信息后还需要由一个网络程序来处理数据,而每个网络程序都有一个独特的端口号作为一道门。

1.socket函数

linux中使用<sys/socket.h>头文件中的socket()函数来创建套接字:int socket(int af,int type,int protocol);

1.af为地址族,IP地址协议,常用为AF_INET(IPV4)和AF_INET6(IPV6),也可以用PF_INET(6)。127.0.0.1表示本机IP

2.type为数据传输方式(套接字类型),常用有SOCK_STREAM(流格式套接字)和SOCK_DGRAM(数据报套接字)

3.protocol为传输协议,常用有IPPROTO_TCP和IPPTOTO_UDP分别是TCP协议和UDP协议

eg:int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

协议处也可改为0系统自动推演协议

2.bind函数

使用socket() 函数创建套接字后,确定套接字的各种属性,之后服务器端用 bind() 函数将套接字与特定的 IP 地址和端口绑定起来,让流经该 IP 地址和端口的数据交给套接字处理。而在客户端要用 connect() 函数建立连接。

bind函数为:int bind(int sock, struct sockaddr *addr, socklen_t addrlen);    sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
  1. //创建sockaddr_in结构体变量
  2. struct sockaddr_in serv_addr;
  3. memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
  4. serv_addr.sin_family = AF_INET; //使用IPv4地址
  5. serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
  6. serv_addr.sin_port = htons(1234); //端口
  7. //将套接字和IP、端口绑定
  8. bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

使用sockaddr_in结构体,之后强制类型转换为sockaddr

sockaddr_in结构体的成员变量为

  1. struct sockaddr_in{
  2. sa_family_t sin_family; //地址族(Address Family),也就是地址类型
  3. uint16_t sin_port; //16位的端口号
  4. struct in_addr sin_addr; //32位IP地址
  5. char sin_zero[8]; //不使用,一般用0填充
  6. };

1.sin_family和socket()函数第一个参数含义相同,取值也一致

2.sin_port为端口号,2字节长度,取值可为0~65536,但0~1023端口一般有系统分配给特殊程序,所以取值在1024~65536

3.sin_addr是struct in_addr结构体的变量,历史遗留问题。因为in_addr中唯一的成员类型为整数,但是ip是一个字符串所以要用inet_addr()函数转换。

4.sin_zero[8]为多余的8个字节,没有用,一般用memset()函数填充为0.

上方提到的结构体类型转换

因为sockaddr结构体中将ip地址和端口号合在了一起,但是没有相关函数去转换想要的形式。但是两个结构体长度相同所以转换较为方便。

客户端所使用的connect()函数:int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);参数同bind()函数

3.listen()函数

对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,之后调用 accept() 函数,达到随时响应客户端的请求。

listen()函数为:int listen(int sock, int backlog);       sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。

被动监听,就是当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

请求队列:当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列。缓冲区的长度可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据需求来定,并发量小的话可以是10或者20。如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误,对于 Windows,客户端会收到 WSAECONNREFUSED 错误。

listen()函数只是让套接字处于监听状态而接收请求要用到accept()函数。

accept()函数为:int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);      它的参数与 listen()函数的参数相同。accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字。

4.数据的发送和接收

Linux 中不区分套接字文件和普通文件,使用 write() 可以向套接字中写入数据,使用 read() 可以从套接字中读取数据。两台计算机之间的通信相当于两个套接字之间的通信,在服务器端用 write() 向套接字写入数据,客户端就能收到,然后再使用 read() 从套接字中读取出来,就完成了一次通信。

write()函数为:ssize_t write(int fd, const void *buf, size_t nbytes);    fd 为要写入的文件的描述符,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。write() 函数会将缓冲区 buf 中的 nbytes 个字节写入文件 fd,成功则返回写入的字节数,失败则返回 -1.

read()函数为:ssize_t read(int fd, void *buf, size_t nbytes);     fd 为要读取的文件的描述符,buf 为要接收数据的缓冲区地址,nbytes 为要读取的数据的字节数。read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。

代码

服务器端代码 server.cpp:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8.  
  9. int main(){
  10. //创建套接字
  11. int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  12.  
  13. //将套接字和IP、端口绑定
  14. struct sockaddr_in serv_addr;
  15. memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
  16. serv_addr.sin_family = AF_INET; //使用IPv4地址
  17. serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
  18. serv_addr.sin_port = htons(1234); //端口
  19. bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  20.  
  21. //进入监听状态,等待用户发起请求
  22. listen(serv_sock, 20);
  23.  
  24. //接收客户端请求
  25. struct sockaddr_in clnt_addr;
  26. socklen_t clnt_addr_size = sizeof(clnt_addr);
  27. int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
  28.  
  29. //向客户端发送数据
  30. char str[] = "http://c.biancheng.net/socket/";
  31. write(clnt_sock, str, sizeof(str));
  32.  
  33. //关闭套接字
  34. close(clnt_sock);
  35. close(serv_sock);
  36.  
  37. return 0;
  38. }


客户端代码 client.cpp:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7.  
  8. int main(){
  9. //创建套接字
  10. int sock = socket(AF_INET, SOCK_STREAM, 0);
  11.  
  12. //向服务器(特定的IP和端口)发起请求
  13. struct sockaddr_in serv_addr;
  14. memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
  15. serv_addr.sin_family = AF_INET; //使用IPv4地址
  16. serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
  17. serv_addr.sin_port = htons(1234); //端口
  18. connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  19.  
  20. //读取服务器传回的数据
  21. char buffer[40];
  22. read(sock, buffer, sizeof(buffer)-1);
  23.  
  24. printf("Message form server: %s\n", buffer);
  25.  
  26. //关闭套接字
  27. close(sock);
  28.  
  29. return 0;
  30. }

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————

回声客户端

 

 

使用python中的pymsql完成如下:表结构与数据创建 1. 建立 `users` 表和 `orders` 表。 `users` 表有用户ID、用户名、年龄字段,(id,name,age) `orders` 表有订单ID、订单日期、订单金额,用户id字段。(id,order_date,amount,user_id) 2 两表的id作为主键,`orders` 表用户id为users的外键 3 插入数据 `users` (1, '张三', 18), (2, '李四', 20), (3, '王五', 22), (4, '赵六', 25), (5, '钱七', 28); `orders` (1, '2021-09-01', 500, 1), (2, '2021-09-02', 1000, 2), (3, '2021-09-03', 600, 3), (4, '2021-09-04', 800, 4), (5, '2021-09-05', 1500, 5), (6, '2021-09-06', 1200, 3), (7, '2021-09-07', 2000, 1), (8, '2021-09-08', 300, 2), (9, '2021-09-09', 700, 5), (10, '2021-09-10', 900, 4); 查询语句 1. 查询订单总金额 2. 查询所有用户的平均年龄,并将结果四舍五入保留两位小数。 3. 查询订单总数最多的用户的姓名和订单总数。 4. 查询所有不重复的年龄。 5. 查询订单日期在2021年9月1日至9月4日之间的订单总金额。 6. 查询年龄不大于25岁的用户的订单数量,并按照降序排序。 7. 查询订单总金额排名前3的用户的姓名和订单总金额。 8. 查询订单总金额最大的用户的姓名和订单总金额。 9. 查询订单总金额最小的用户的姓名和订单总金额。 10. 查询所有名字中含有“李”的用户,按照名字升序排序。 11. 查询所有年龄大于20岁的用户,按照年龄降序排序,并只显示前5条记录。 12. 查询每个用户的订单数量和订单总金额,并按照总金额降序排序。
06-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值