0529
Linux简明系统编程
- 〇、一、二、三、四
- 进程间通信(Inter Process Communication) IPC的7种方式
- 五、Linux网络编程
-
- 要用到的头文件
- 补充:几个函数的解释 htons()函数、inet_addr()函数、inet_ntoa()函数
- 第21节课:Linux网络编程简介
- TCP编程 --- 第22、23节课:TCP服务端、客户端编程
- UDP编程 --- 第24、25节课:UDP服务端、客户端编程
- 第26、27节课:并发服务器模型 concurrent_server
- 六、相关链接
〇、一、二、三、四
进程间通信(Inter Process Communication) IPC的7种方式
☆☆☆①任务间的通信 之 管道pipe
☆☆☆②任务间的通信 之 共享内存 shared memory
☆☆☆③任务间的通信 之 消息队列message queue
☆☆☆④任务间的同步 之 信号量semaphore
☆☆☆⑤任务间的同步 之 互斥锁mutex
☆☆☆⑥内核和应用进程间/进程和进程间 传递的控制命令 之 信号signal
☆☆☆⑦socket套接字
这部分内容属于网络编程的范围,具体就是TCP、UDP的实现,见下面的五、Linux网络编程
五、Linux网络编程
要用到的头文件
#include<sys/types.h> //socket()函数、
#include<sys/socket.h>//socket()函数、bind()函数、listen()函数、accept()函数、inet_addr()函数、inet_ntoa()
#include<arpa/inet.h> //htons()函数、inet_addr()函数、inet_ntoa()函数
#include<netinet/in.h>//inet_addr()函数、inet_ntoa()
#include<iostream>
using namespace std;
int main(){
...
return 0;
}
补充:几个函数的解释 htons()函数、inet_addr()函数、inet_ntoa()函数
第21节课:Linux网络编程简介
TCP/IP协议相当于是交通规则,如果要开车上路就要学习交通规则的内容,但并不是学习学习了交通规则就会开车了,还要实际去操作,去练习,才能真正去上路。
开放系统互联参考模型(OSI ReferenecModel),即我们通常所说的网络互联的七层框架。
TCP编程 — 第22、23节课:TCP服务端、客户端编程
socket系统调用
socket()函数 — 创建一个新socket
man socket
socket()函数的功能:创建一个新socket,并返回新创建的socket的文件描述符sockfd。
头文件:
#include<sys/types.h>
#include<sys/socket.h>
第一个参数domain指定了套接字socket的通信domain,常用的参数有AF_INET
和AFINET6
,分别表示IPv4
协议和IPv6
协议;
第二个参数type表示套接字socket的类型,每个socket实现都至少提供了两种socket:流socket和数据报socket。常用的有SOCK_STREAM
和 SOCK_DGRAM
,分别标识TCP
协议和UDP
协议,分别称作 TCP socket
和 UDP socket
。
第三个参数protocol表示协议,一般就写个0。
返回值:如果函数调用成功,就返回新创建的套接字socket的文件描述符sockfd
(int类型),否认就返回-1。
bind()函数 — 给socket绑定一个地址
man bind
函数的功能:bind函数用来将一个socket绑定到一个地址上。
头文件:
#include<sys/types.h>
#include<sys/socket.h>
第一个参数sockfd
就是函数socket()的返回值,表示套接字的文件描述符;
第二个参数addr是一个地址,它指向地址结构体const struct sockaddr*
,bind()函数就是要把前两个参数绑定到一起;
第三个参数addrlen表示地址结构体的大小。
通用socket地址结构体
struct sockaddr
地址结构体的通用格式:
struct sockaddr{
sa_family sa_family;//address family地址族
char sa_data[14];//socket address套接字地址
}
sockaddr
结构体的用途就是将各种domain特定的地址结构体转换成单个类型以供socket系统调用中的各个参数使用,相当于是所有domain特定的地址结构体的模板,其中每个地址结构均以 (与sockaddr结构体中sa_family
字段对应的) family
字段打头。
Internet domain socket地址有两种:IPv4和IPv6。
IPv4 socket地址存储在一个sockaddr_in结构中,
struct in_addr{
//IPv4地址(4字节,32位)
in_addr_t s_addr;//32位无符号整型
}
struct sockaddr_in{
//IPv4 socket地址
sa_family sin_family;//地址族(AF_INET),对应sockaddr结构体中sa_family字段
in_port_t sin_port;//端口号(2字节,16位)
struct in_addr sin_addr;//IPv4地址(4字节,32位)
unsigned char _pad[X];//补全到sockaddr结构体中char sa_data[14]的大小,即14个字节
}
sin_port
和sin_addr
字段分别表示端口号和IP地址,它们都是网络字节序network byte order
,所以需要借助htons
函数进程转换。
man htons
头文件:#include<arpa/inet.h>
网络字节序和主机序(大端和小端)
参考链接:网络字节序和主机序(大端和小端)
什么是大端和小端?
小端:低位数据放在低地址
大端:高位数据放在低地址
比如 0x12 34 56 78
,其中78
是低位,12
是高位。该数的右侧是低地址,左侧是高地址。也就是说低位对应低地址(小端字节序)
如果0x12 34 56 78
是小端字节序,那么0x78 56 34 12
就是大端字节序,它把高位12
放在最右侧的低地址(大端字节序)
一般,
主机字节序 是 小端模式
网络字节序 是 大端模式
而一个字节中的比特序,是低位放在低地址的。
为什么要有大端小端之分?
小端: 在内存中通常低位放在低地址。比如说0x12 34 56 78,从右往左进行程序处理速度更快。
大端:网络字节序,0x78 56 32 12将字节序反了一下。
但是一般人们阅读习惯就是从左往右。举个例子,对于网络字节序0x78 56 34 12,网络通常以字节流的方式传输,先接受到的是低地址的12,接着是34,…这样就能接收到的顺序就是12345678,也就是正常的顺序。如果直接按照0x12 34 56 78进行传输,会先接受到78然后是56…这个样子显然是不方便人为的阅读。
补充:socket开发中"INADDR_ANY"的含义是什么?
参考链接:点这里
listen()函数 — 监听socket连接
man listen
listen函数用来监听套接字的连接。
第一个参数是套接字文件描述符sockfd
,也就是socket函数的返回值;
第二个参数定义(用来存放处于挂起pending状态的套接字连接的)队列queue的最大长度maximum length
,当队列为满full的状态时来了一个连接请求,就会出错。
返回值:函数调用成功就返回0,否则返回-1。
connect()函数 — 发起socket连接
man connect
第一个参数是sockfd,表示socket的文件描述符;
第二参数是sockaddr *
类型,表示要连接的服务器的地址结构体,这里的IP地址就需要手动输入了;
第三个参数是地址结构体的大小,sizeof(struct sockaddr);
返回值:函数调用成功就返回0,否则返回-1。
注意:第二个参数直接输入一个IP地址不可以,需要用inet_addr()
函数进行转换,把IPv4点分十进制格式的字符串转换为网络字节序二进制格式的地址。
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
accept()函数 — 接受socket连接
man accept
第一个参数依然是sockfd
,表示socket的文件描述符;
第二参数是sockaddr *
类型,表示想要连接服务器的客户机的地址结构体,这是个输出类型的参数,即accept调用完成后,系统会给这个参数赋上想要连接服务器的客户机的地址结构体的地址;
第三个参数是地址结构体的大小,sizeof(struct sockaddr);
返回值:函数调用成功就返回一个非负int值
,该值是所接受套接字的文件描述符socketfd
。
如果要打印想要连接服务器的客户机的地址,需要用到一个函数inet_ntoa()
,将Internet主机的二进制格式的地址(网络字节序)转换为IPv4点分十进制格式的字符串。
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
write() / send()函数 — 通过socket发送信息
man send
send()函数可以通过一个socket发送消息。
第一个参数是sockfd,表示通过哪一个套接字来发送消息;
第二个参数表示要发送的信息;
第三个参数表示要发送的信息的长度/大小;
第四个参数,一般写0。
read() / recv()函数 — 通过socket接收信息
man recv
第一个参数是sockfd,表示通过哪个socket来接收信息;
第二个参数是用来存放接收信息的缓冲区;
第三个参数是缓冲区的大小;
第四个参数一般写0。
返回值:如果函数调用成功就返回0,否则返回-1。
close()函数 — 断开连接
关闭一个文件描述符,这里是指断开服务器和客户机之间的连接。
示例1:
服务器一直监听,只要有客户机发起连接,就发送一条测试信息给客户机;客户机收到测试信息后打印出来。
server.cpp
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
#define SIZE 100
int main(){
struct sockaddr_in server_addr, client_addr;//sockaddr_in结构体变量
int sockfd;//socket文件描述符
socklen_t addrLen = sizeof(struct sockaddr);//地址结构体的大小
int client_sockfd;//socket文件描述符
char buf1[SIZE] = "这是服务器发来的测试信息...";//要发送的内容
//1.socket()
sockfd = socket(2, SOCK_STREAM, 0);//创建一个socket PF_INET
//2.bind()
server_addr.sin_family = 2;//AF_INET;
server_addr.sin_port = htons(8800);//端口号
server_addr.sin_addr.s_addr = INADDR_ANY;//((in_addr_t) 0x00000000)
bind(sockfd, (struct sockaddr *)&server_addr, addrLen);//给新创建的socket绑定一个地址
//3.listen()
listen(sockfd, 10);//监听队列大小设置为10
//服务器一般不关机,一直处在监听状态:
while(1){
cout << "服务器一直在等待客户机连接..." << endl;
//4.accept()
client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrLen);
cout << "请求连接的客户机IP地址:" << inet_ntoa(client_addr.sin_addr) << endl;
//5.send()
send(client_sockfd, buf1, SIZE, 0);
//6.close()
cout << "断开socket连接..." << endl;
close(client_sockfd);//断开socket连接
}
close(sockfd);
return 0;
}
client.cpp
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
#define SIZE 100
int main(int argc, char *argv[]){
//char
if(argc < 2){
//参数个数argument count 小于2就提醒用户应该按下面的格式输入
cout << "请输入 ./client [服务器IP地址]" << endl;
return 0;
}
struct sockaddr_in server_addr;//sockaddr_in结构体变量
int sockfd;//socket文件描述符
socklen_t addrLen = sizeof(struct sockaddr);//地址结构体的大小
char buf1[SIZE];//用来接收的buffer
//1.socket()
sockfd = socket(2, SOCK_STREAM, 0);//创建一个socket PF_INET
//2.connect()
server_addr.sin_family = 2;//AF_INET;
server_addr.sin_port = htons(8800);//端口号
server_addr.sin_addr.s_addr = inet_addr((const char *)argv[1]);
connect(sockfd, (sockaddr *)&server_addr, addrLen);
//3.recv()
recv(sockfd, buf1, SIZE, 0);
cout << "从服务器发来的内容:" << buf1 << endl;
//6.close()
//cout << "断开socket连接..." << endl;
close(sockfd);//断开socket连接
return 0;
}
编译运行:
打开两个终端,分别运行 ./server和 ./client.cpp
示例2:
服务器处于监听状态,有客户机发来连接请求就accept,然后接收客户机发来的消息.
参考链接:网络编程之TCP简单示例
server.cpp
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
#define SIZE 100
int main(){
int sockfd;//socket文件描述符
//1.socket()
sockfd = socket(2, SOCK_STREAM, 0);//创建一个socket PF_INET
if(sockfd < 0) {
perror("socket"); cout << "socket创建失败!" << endl;}
else cout << "socket创建成功,sockfd = " << sockfd << endl;
//2.bind()
struct sockaddr_in server_addr;//sockaddr_in结构体变量
server_addr.sin_family = 2;//AF_INET;
server_addr.sin_port = htons(8800);//端口号
server_addr.sin_addr.s_addr = INADDR_ANY;//((in_addr_t) 0x00000000)
socklen_t addrLen = sizeof(struct sockaddr);//地址结构体的大小
int res = bind(sockfd, (struct sockaddr *)&server_addr, addrLen);//给新创建的socket绑定一个地址
if(res < 0) {
perror("bind"); cout << "bind失败!" << endl; }
else cout << "bind成功!" << endl;
//3.listen()
res = listen(sockfd, 10);//监听队列大小设置为10
if(res < 0) perror("listen");
else cout << "listening..." << endl;
//服务器一般不关机,一直处在监听状态://cout << "服务器一直在等待客户机连接..." << endl;
struct sockaddr_in client_addr;
int client_sockfd;//socket文件描述符
//4.accept()
client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrLen);//NULL, NULL);//
if(client_sockfd == -1) {
perror("accept"); cout << "accept失败!" << endl;}
else cout << "accept成功,client_sockfd = " << client_sockfd << endl;
cout << "请求连接的客户机IP地址:" << inet_ntoa(client_addr.sin_addr) << endl;
char buf1[SIZE] = "这是服务器发来的测试信息...";//要发送的内容
char buf2[SIZE];//接收内容的buffer
for(int i = 0; i < 8; ++i){
sleep(1);//1秒刷新一次
//5.send()
//send(client_sockfd, buf1, SIZE, 0);
//5.recv()
res = recv(client_sockfd, buf2, SIZE, 0);
if(res < 0) {
perror("bind"); cout << "recv失败!" << endl