Linux 网络编程 --套接字编程
Posted 蚍蜉撼树谈何易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 网络编程 --套接字编程相关的知识,希望对你有一定的参考价值。
套接字编程
认识端口号
端口号(port)是传输层协议的内容.
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
端口号与进程号的对应关系
1)一个进程可以绑定多个端口号
2)一个端口号只可以被一个进程绑定
注:若是进程先绑定一个端口号,而后在fork一个子进程,这样的话就能够是实现多个进程绑定一个端口号,可是两个不一样的进程绑定同一个端口号是不能够的
socket理解
- socket即为套接字,在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一的标识网络通讯中的一个进程,“IP地址+TCP或UDP端口号”就为socket。
- 在TCP协议中,建立连接的两个进程(客户端和服务器)各自有一个socket来标识,则这两个socket组成的socket pair就唯一标识一个连接。
- socket本身就有“插座”的意思,因此用来形容网络连接的一对一关系,为TCP/IP协议设计的应用层编程接口称为socket API。
网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分.
1.发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出; 2.接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存; 3.因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. 4.TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节. 5.不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据; 6.如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
#include<arpa/inet.h>
//将主机字节序转换为网络字节序
uint32_t htonl(uint32_t hostlong);//将32长整数从主机字节序转换为网络字节序,
//如果主机字节序是小端,则函数会做相应大小
//端转换后返回;如果主机字节序是大端,则函
//数不做转换,将参数原封不动返回。。。下同
uint16_t htons(uint16_t hostshort);
//将网络字节序转换为主机字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
// h表示主机(host),n表示网络(net),l表示32位长整数,s表示16短整数。
UDP实现
所用结构体
介绍几个函数:
代码:
udp_server.hpp
1 #pragma once
2 #include<iostream>
3 #include<string>
4 #include<stdlib.h>
5 #include<cstdio>
6 #include<sys/types.h>
7 #include<sys/socket.h>
8 #include<netinet/in.h>
9 #include<arpa/inet.h>
10 #include<unistd.h>
11 #include<map>
12 class udpServer
13
14 private:
15 int port;
16 int sock;
17 std::map<std::string,std::string>dict;
18 public:
19 udpServer(int _port=8080)
20 :port(_port)
21
22 dict.insert(std::pair<std::string,std::string>("apple","苹果"));
23 dict.insert(std::pair<std::string,std::string>("banana","香蕉"));
24 dict.insert(std::pair<std::string,std::string>("book","书"));
25 dict.insert(std::pair<std::string,std::string>("classroom","教室"));
26
27
28 void udp_init()
29
30 sock=socket(AF_INET,SOCK_DGRAM,0);
31 std::cout<<sock<<std::endl;
32 if(sock<0)
33
34 std::cout<<"创立套接字失败"<<std::endl;
35 exit(1);
36
37 struct sockaddr_in local;
38 //协议家族
39 local.sin_family=AF_INET;
40 //让操作系统去分配ip,不指定
41 local.sin_addr.s_addr=INADDR_ANY;
42 //将主机字节转化为网络字节序
43 local.sin_port=htons(port);
44 //bind 作用:将创建出来的套接字和网卡、端口进行绑定
45 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
46
47 std::cerr<<"bind error"<<std::endl;
48 exit(2);
49
50 std::cout<<"服务器已启动"<<std::endl;
51
52
53 void server_start()
54
55 char msg[64];
56 while(1)
57
58 msg[0]='\\0';
59 struct sockaddr_in end_point;
60 socklen_t len=sizeof(end_point);
61 ssize_t s=recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
62 if(s>0)
63
64 msg[s]='\\0';
65 char buf[16];
66 //先将其端口号转化出来 ntohs n表示网络 h表示host主机 s表示16位
67 sprintf(buf,"%d",ntohs(end_point.sin_port));
68 // char *inet_ntoa (struct in_addr); 两个作用:1.将数据转化为点分十进制的字符 2.将网络序列转化位主机序列。
69 std::stringcli=inet_ntoa(end_point.sin_addr);
70 cli+=':';
71 cli+=buf;
72 std::cout<<cli<<" #"<<msg<<std::endl;
73 std::string echo="unkonwn";
74 auto it=dict.find(msg);
75 if(it!=dict.end())
76
77 echo=dict[msg];
78
79 //对服务器收到的东西做出应答
80 sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)&end_point,len);
81
82 else
83
84 std::cout<<"i can not get it"<<std::endl;
85
86
87
88 ~udpServer()
89
90 close(sock);
91
92
93
94 ;
udp_client.hpp
1 #pragma once
2 #include<iostream>
3 #include<string>
4 #include<sys/types.h>
5 #include<sys/socket.h>
6 #include<netinet/in.h>
7 #include<arpa/inet.h>
8 #include<unistd.h>
9 #include<map>
10 class udpClient
11
12 private:
13 std::string ip;
14 int port;
15 int sock;
16 public:
17 udpClient(std::string _ip,int _port)
18 :ip(_ip),port(_port)
19
20 void Client_init()
21
22 sock=socket(AF_INET,SOCK_DGRAM,0);
23
24
25 void Client_Start()
26
27 std::string msg;
28 struct sockaddr_in peer;
29 peer.sin_family=AF_INET;
30 peer.sin_port=htons(port);
31 // sin_addr_t inet(const char*co);//将目标字符串转为网络序列
32 peer.sin_addr.s_addr=inet_addr(ip.c_str());
33 while(1)
34
35 std::cout<<"please enter#";
36 std::cin>>msg;
37 if(msg=="quit")
38
39 break;
40
41 sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
42 std::cout<<"发送成功"<<std::endl;
43 char echo[128];
44 ssize_t s=recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
45 if(s>0)
46
47 echo[s]='\\0';
48 std::cout<<"server#"<<echo<<std::endl;
49
50
51
52
53
54
55 ~udpClient()
56
57 close(sock);
58
59
60
61 ;
udp_server.cpp
1 #include"udp_server.hpp"
2 void Usage(std::string proc)
3
4 std::cout << "Usage: " << proc << " port" <<std::endl;
5
6
7
8 int main(int argc, char *argv[])
9
10 if(argc != 2)
11
12 Usage(argv[0]);
13 exit(1);
14
15
16 udpServer *up = new udpServer(atoi(argv[1]));
17 up->udp_init();
18 up->server_start();
19 delete up;
20 return 0;
21
22
udp_client.cpp
1 #include"udp_client.hpp"
2 void Usage(std::string proc)
3
4 std::cout <<"Usage: "<< proc << " svr_ip svr_port" << std::endl;
5
6
7
8 int main(int argc, char *argv[])
9
10 if(argc != 3)
11
12
13 Usage(argv[0]);
14 exit(1);
15
16
17 udpClient *rq=new udpClient(argv[1],atoi(argv[2]));
18 rq->Client_init();
19 rq->Client_Start();
20 delete rq;
21 return 0;
22
23
~
makefile
1 all:Client Server
2 Client:udp_clent.cpp
3 g++ $^ -o $@ -std=c++11
4 Server:udp_server.cpp
5 g++ $^ -o $@ -std=c++11
6 .PHONY:clean
7 clean:
8 rm -f Server Client
tcp
Api
区别于tcp,此处创建的套接字描述符为监听套接字
为什么需要监听套接字?监听套接字的作用
监听套接字就是个牵线指路的,你实质上是跟它指的那个人说话。因为你要找的那个人不可能随时等你来,而监听套接字就是专职等你来问,它回答你要找的人在哪,并唤醒你要找的人,于是通话就建立起来了,就像现实生活中的接线员一样。实际在进行通信的时候是采用accept函数的返回值作为通信套接字的,而不是监听套接字。
connect:触发链接建立,底层进行三次握手。
netstat命令
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服務状态
-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。
inet_ntoa()所带来的线程安全的问题
使用inet_ntoa的话,就不能够在同一个函数的几个参数里面出席那两次inet_ntoa,或者是第一个inet_ntoa未使用结束之前,不要使用第二个。
解决方案
#include <arpe/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
//将点分十进制的ip地址转化为用于网络传输的数值格式
//返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
//将二进制整数格式转化为点分十进制的ip地址格式
//返回值:若成功则为指向结构的指针,若出错则为NULL
复制代码
(1)这两个函数的family参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。如果,以不被支持的地址族作为family参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.
(2)第一个函数尝试转换由strptr指针所指向的字符串,并通过addrptr指针存放二进制结果,若成功则返回值为1,否则如果所指定的family而言输入字符串不是有效的表达式格式,那么返回值为0.
(3)inet_ntop进行相反的转换,从数值格式(addrptr)转换到表达式(strptr)。inet_ntop函数的strptr参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。len参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。如果len太小,不足以容纳表达式结果,那么返回一个空指针,并置为errno为ENOSPC。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main (void)
char IPdotdec[20]; //存放点分十进制IP地址
struct in_addr s; // IPv4地址结构体
// 输入IP地址
printf("Please input IP address: ");
scanf("%s", IPdotdec);
// 转换
inet_pton(AF_INET, IPdotdec, (void *)&s);
printf("inet_pton: 0x%x\\n", s.s_addr);
// 反转换
inet_ntop(AF_INET, (void *)&s, IPdotdec, 16);
printf("inet_ntop: %s\\n", IPdotdec);
C/S模式
cs即:client/server,是服务器客户端结构。是一种 “一对多” 的模式,一台服务器,处理多个客户端发来的请求,完成了业务逻辑之后,再返回给客户端一些信息。其中,服务器不会主动发起请求,都是被动处理的。 像一头牛,拉一下绳子走几步;而客户端则主要是提供一个界面,给你点击一些功能按钮,其实每一个按钮就是一个请求,发送到服务器中给你实现这个请求,再把结果返回给你。就像你有一道数学题目不会,打开客户端(一个app,或者exe)然后输入你的问题,点击解答按钮,就有一个请求通过网络到了服务器,服务器做出了这道题,把答案返回给你,你的客户端界面再显示出这道题的答案,这就是所谓的cs模式的工作流程了。
C/S模式的优点
1、C/S模式将应用与服务分离,系统具有稳定性和灵活性。
2、C/S模式配备的是点对点的结构模式,适用于局域网,有可靠的安全性 。
3、由于客户端实现与服务器端的直接连接,没有中间环节,因此响应速度快。
(1)应用服务器运行数据负荷较轻。最简单的C/S体系结构的数据库应用由两部分组成,即客户应用程序和数据库服务器程序。二者可分别称为前台程序与后台程序。运行数据库服务器程序的机器,也称为应用服务器。一旦服务器程序被启动,就随时等待响应客户程序发来的请求;客户应用程序运行在用户自己的电脑上,对应于数据库服务器,可称为客户电脑,当需要对数据库中的数据进行任何操作时,客户程序就自动地寻找服务器程序,并向其发出请求,服务器程序根据预定的规则作出应答,送回结果,应用
以上是关于Linux 网络编程 --套接字编程的主要内容,如果未能解决你的问题,请参考以下文章
linux怎么配置网络设置(linux怎么配置网络 连接外网)