2 Socket编程-接口
2.1 接口转换
转接口的换操作主要分为三类:字节序转换操作、IP地址转换操作和主机名转换操作。
2.1.1 字节序转换操作
函数 |
含义 |
作用 |
ntohs() |
network to host short |
把unsigned short 类型从网络序转换到主机序 |
ntohl() |
network to host long |
把unsigned long 类型从网络序转换到主机序 |
函数 |
含义 |
作用 |
htons() |
host to network short |
把unsigned short 类型从主机序转换到网络序 |
htonl() |
host to network long |
把unsigned long 类型从主机序转换到网络序 |
实例:
#include <iostream>
#include <cstring>
#include <iomanip>
#include <arpa/inet.h>
using namespace std;
void Hex(int n){
char bytes[2];
memcpy(bytes,&n,2);
cout << showbase << setw(2) << setfill('0') << hex << (int)bytes[0] << setw(2) << (int)bytes[1] << endl;
}
int main(){
uint16_t n;
cin >> n;
Hex(n);
uint16_t m = htons(n);
Hex(m);
uint16_t t = ntohs(m);
Hex(t);
}
[root@localhost socket]# ./a.out
16
0x1000
000x10
0x1000
2.1.2 IP地址转换操作
函数 |
功能 |
特点 |
int inet_aton(const char *string, struct in_addr*addr) |
点分十进制数串转网络字节序长整型 |
IPv4专用 |
in_addr_t inet_addr(const char* string) |
点分十进制数串转网络字节序长整型 |
IPv4专用 |
char* inet_ntoa(struct in_addr addr) |
网络字节序长整型转点分十进制数串 |
IPv4专用 |
int inet_pton(int af, const char *src, void *dst) |
点分十进制数串转网络字节序长整型 |
IPv4/IPv6通用(推荐) |
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) |
网络字节序长整型转点分十进制数串 |
IPv4/IPv6通用(推荐) |
实例:
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
using namespace std;
int main(){
string s;
cin >> s;
in_addr addr;
inet_aton(s.c_str(),&addr);
cout << hex << addr.s_addr << endl;
cout << inet_ntoa(addr) << endl;
cout << inet_addr(s.c_str()) << endl;
}
[root@localhost socket]# ./a.out
192.168.0.1
100a8c0
192.168.0.1
100a8c0
结构体 |
功能 |
特性 |
struct sockaddr |
套接字地址结构 |
IPv4/IPv6通用 |
struct sockaddr_in |
IPv4套接字地址结构 |
IPv4专用 |
struct in_addr |
IPv4地址结构 |
IPv4专用 |
in_addr_t |
IPv4地址类型 |
IPv4专用 |
struct sockaddr_in6 |
IPv6套接字地址结构 |
IPv6专用 |
struct sockaddr {
unsigned short sa_family; // 套接字地址簇类型,为AF_INET
char sa_data[14]; // 套接字地址数据(14位的协议地址)
};
struct sockaddr_in{
short sin_family; // 套接字地址簇类型,为AF_INET
unsigned short sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IP地址,网络字节序
unsigned char sin_zero[8];// 填充字节
};
sin_zero[8]
用来保证结构体struct sockaddr_in
的大小和结构体struct sockaddr
的大小相等。
struct in_addr {
in_addr_t s_addr;
};
typedef unsigned int in_addr_t;
struct sockaddr_in6{
uint8_t sin6_len; //IPv6 为固定的24 字节长度
sa_family_t sin6_family; //套接字地址簇类型,为AF_INET6
in_port_t sin6_port; //16 位端口号,网络字节序
uint32_t sin6_flowinfo; //32 位流标签
struct in6_addr sin6_addr; //128 位IP地址
}
2.1.3 主机名转换操作
函数 |
功能 |
struct hostent *gethostbyname(const char *hostname) |
主机名转地址 |
struct hostent *gethostbyaddr(const char * addr, int len, int type) |
地址转主机名 |
struct hostent *gethostbyaddr(const char * addr, int len, int type) |
地址转主机名 |
参数 |
含义 |
h_name |
主机名字 |
h_aliases |
以空指针结尾的主机别名队列 |
h_addrtype |
地址类型。AF_INET/AF_INET6 |
h_length |
地址长度。在AF_INET类型地址中为4 |
h_addr |
第一个IP地址 |
h_addr_list |
以空指针结尾的IP地址的列表 |
2.2 socket
操作
2.2.1 创建
int socket(int domain, int type, int protocol)
参数 |
含义 |
domain |
协议域 AF_INET :IPv4;AF_INET6 :IPv6;AF_LOCAL :Unix域 |
type |
类型 SOCK_STREAM :流式套接字;SOCK_DGRAM :数据报套接字;SOCK_RAW :原始套接字 |
protocol |
协议 0 :自动根据type 匹配协议;IPPROTO_TCP/IPPROTO_UDP
|
返回值 |
含义 |
-1 |
失败 |
>0 |
socket描述符 |
2.2.2 关闭
int close(int sockfd)
int shutdown(int sockfd,int howto)
参数 |
含义 |
sockfd socket |
套接字 |
howto |
关闭方式 |
方式 |
值 |
含义 |
SHUT_RD |
0 |
关闭连接的读 |
SHUT_WR |
1 |
关闭连接的写 |
SHUT_RDWR |
2 |
连接的读和写都关闭 |
2.2.3 属性
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
参数 |
含义 |
sockfd |
套接字描述符 |
level |
选项层次 |
optname |
选项 |
optval |
选项值指针 |
optlen |
optval缓冲区长度 |
参数 |
含义 |
SOL_SOCKET |
通用套接字选项 |
IPPROTO_TCP |
TCP选项 |
IPPROTO_IP |
IP选项 |
IPPROTO_IPV6 |
IPv6选项 |
选项分为SOL_SOCKET
级别和IPPROTO_IP
级别两个级别
2.2.4 获取
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen)
参数 |
含义 |
sockfd |
套接字描述符 |
level |
选项层次 |
optname |
选项 |
optval |
选项值指针 |
optlen |
optval缓冲区长度 |
2.2.5 绑定
int bind(int socket, const struct sockaddr* address, socklen_t address_len)
参数 |
含义 |
socket |
套接字描述符 |
address |
地址和端口号 |
address_len |
address缓冲区的长度 |
返回值 |
含义 |
0 |
成功 |
SOCKET_ERROR |
失败 |
2.2.6 监听
int listen(int sockfd, int backlog)
参数 |
含义 |
sockfd |
监听的socket描述符 |
backlog |
排队的最大连接个数 |
2.2.7 连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 |
含义 |
sockfd |
客户端的socket描述字 |
addr |
服务器的socket地址 |
addrlen |
服务器的socket地址的长度 |
2.2.8 接受
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
参数 |
含义 |
sockfd |
服务器的socket描述符,监听socket描述符 |
addr |
客户端的socket地址 |
addrlen |
客户端的socket地址的长度 |
注:说明
如果不需要获取客户端的套接字地址,后两个参数可设置为NULL。
int connfd = accept(listenfd,NULL,NULL);
如果需要获取,则按照如下方式设置:
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
socklen_t remote_addr_len = sizeof(remote_addr);
int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
2.2.9 发送
方式1:
ssize_t write(int fd, const void *buf, size_t len);
参数 |
含义 |
fd |
文件描述符 |
buf |
写入数据 |
len |
写入数据的长度 |
方式2:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数 |
含义 |
sockfd |
sockfd文件描述符 |
buf |
写入数据 |
len |
写入数据的长度 |
flags |
通常为0 |
方式3:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数 |
含义 |
sockfd |
sockfd文件描述符 |
buf |
写入数据 |
len |
写入数据的长度 |
flags |
通常为0 |
dest_addr |
目标socket地址 |
addrlen |
目标socket地址长度 |
2.2.10 接收
方式1:
ssize_t read(int fd, void *buf, size_t len);
参数 |
含义 |
fd |
文件描述符 |
buf |
读取数据 |
len |
读取数据的长度 |
返回值 |
含义 |
0 |
读到文件的结束 |
>0 |
实际所写的字节数 |
<0 |
出错 |
方式2:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数 |
含义 |
fd |
文件描述符 |
buf |
读取数据 |
len |
读取数据的长度 |
flags |
通常为0 |
返回值 |
含义 |
0 |
读到文件的结束 |
>0 |
实际所写的字节数 |
<0 |
出错 |
方式3:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen)
参数 |
含义 |
fd |
文件描述符 |
buf |
读取数据 |
len |
读取数据的长度 |
flags |
通常为0 |
dest_addr |
目标socket地址 |
addrlen |
目标socket地址长度 |
返回值 |
含义 |
0 |
读到文件的结束 |
>0 |
实际所写的字节数 |
<0 |
出错 |
2.3 客户端与服务器的通信
2.3.1 特殊地址设置
特殊地址 |
ipv4 |
ipv6 |
通配地址 |
in_addr.sin_addr.s_addr = htonl(INADDR_ANY) |
in6_addr.sin6_addr=in6addr_any |
回环地址 |
in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK) |
in6_addr.sin6_addr=in6addr_loopback |
广播地址 |
in_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST) |
无广播 |
2.3.2 原则
- 客户端/发送端
必须指定连接/发送的IP(广播地址、回环地址或者某个具体地址)。
必须指定连接/发送的port。
- 服务器/接受端
IP指定为通配地址、回环地址或者某个具体地址。
必须指定绑定监听/接受的port。
Linux查看网络连接状态指令
netstat
:查看网络连接状态、socket端口打开状态
选项 |
作用 |
-antp |
查看tcp的状态 |
-anup |
查看udp的状态 |
2.4 TCP基本流程
C/S |
函数 |
Server |
socket() 、bind() 、listen() 、accept() 、recv()/read() andsend()/write() 、close()
|
Client |
socket() 、connect() 、send()/write() andrecv()/read() 、close()
|
int main(){
//1.打开套接字
fd = socket();
//2.连接服务器
connect(fd,...);
//3.写入/读取数据
write(fd,...);
read(fd,...);
//4.关闭套接字
close(fd);
}
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(){
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd){
perror("open socket error");
return 1;
}
//设置套接字
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton("127.0.0.1",&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(8080);//addr的端口号 = 主机序转化为网络序
//2.连接服务器
int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res){
perror("connect error");
return 1;
}
//3.写入/读取数据
string s;
cin >> s;
write(fd,s.c_str(),s.size()+1);
char buffer[256] = {0};
read(fd,buffer,256);
cout << buffer << endl;
//4.关闭套接字
close(fd);
}
int main(){
//1.打开套接字
fd = socket();
//2.绑定
bind(fd,...);
//3.监听
listen(fd,backlog);
//4.连接客户端
connfd = accept(fd,...);
//5.读写数据
read(connfd);
write(connfd);
//6.关闭套接字
close(connfd);
close(fd);
}
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(){
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd){
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton("127.0.0.1",&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(8080);//addr的端口号 = 主机序转化为网络序
//2.绑定
int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res){
perror("bind error");
return 1;
}
//3.监听
res = listen(fd,4);
if(-1 == res){
perror("listen error");
return 1;
}
//4.连接客户端
int connfd = accept(fd,NULL,NULL);
if(-1 == connfd){
perror("accept error");
return 1;
}
//5.读写数据
char buffer[256] = {0};
read(connfd,buffer,256);
cout << buffer << endl;
string s;
cin >> s;
write(connfd,s.c_str(),s.size()+1);
//6.关闭套接字
close(connfd);
close(fd);
}
执行结果:
[root@localhost socket]# ./client
zjl
123
...
...
...
[root@localhost socket]# ./sever
zjl
123
步骤:执行./sever
开启服务器,执行./client
开启客户端,客户端向服务器发送数据,服务器接收到之后从服务器端向客户端发送数据。
2.4.1 实例1:采用父子进程的方式
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.连接服务器
int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("connect error");
return 1;
}
//3.读写数据
if(fork()) {
string s;
while(cin >> s) {
write(fd,s.c_str(),s.size()+1);
}
} else {
for(;;) {
char buffer[256] = {0};
read(fd,buffer,256);
cout << buffer << endl;
}
}
//4.关闭套接字
close(fd);
}
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.绑定
int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("bind error");
return 1;
}
//3.监听
res = listen(fd,4);
if(-1 == res) {
perror("listen error");
return 1;
}
//4.打开连接套接字
int connfd = accept(fd,NULL,NULL);
if(-1 == connfd) {
perror("accept error");
return 1;
}
//5.读写数据
if(fork()) {
for(;;) {
char buffer[256] = {0};
read(connfd,buffer,256);
cout << buffer << endl;
}
} else {
string s;
while(cin >> s) {
write(connfd,s.c_str(),s.size()+1);
}
}
//6.关闭套接字
close(connfd);
close(fd);
}
[root@localhost 2]# ./client 127.0.0.1 8080
as
as
1
2
3
4
45
q
a
s
d
a
...
...
[root@localhost 2]# ./sever 127.0.0.1 8080
as
as
1
2
3
4
45
q
a
s
d
a
2.4.2 实例2:采用C++多线程的方式
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <thread>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.连接服务器
int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("connect error");
return 1;
}
//3.读写数据
thread t([fd](){
for(;;) {
char buffer[256] = {0};
int n = read(fd,buffer,256);
if(0 == n){
cout << "sever exit!" << endl;
break;
}
cout << buffer << endl;
}
});
t.detach();
string s;
while(cin >> s) {
write(fd,s.c_str(),s.size()+1);
}
//4.关闭套接字
close(fd);
}
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <thread>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.绑定
int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("bind error");
return 1;
}
//3.监听
res = listen(fd,4);
if(-1 == res) {
perror("listen error");
return 1;
}
//4.打开连接套接字
int connfd = accept(fd,NULL,NULL);
if(-1 == connfd) {
perror("accept error");
return 1;
}
//5.读写数据
thread t([connfd]() {
for(;;) {
char buffer[256] = {0};
int n = read(connfd,buffer,256);
if(0 == n) {
cout << "client exit!" << endl;
break;
}
cout << buffer << endl;
}
});
t.detach();
string s;
while(cin >> s) {
write(connfd,s.c_str(),s.size()+1);
}
//5.关闭套接字
close(connfd);
close(fd);
}
[root@localhost 2]# ./client 127.0.0.1 8081
asd
1
2
2
3
4
a
v
d
d
^C
...
...
[root@localhost 2]# ./sever 127.0.0.1 8081
asd
1
2
2
3
4
a
v
d
d
client exit!
^C
2.4.3 实例3:多个客户端和服务器之间的通信
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <thread>
#include <vector>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.绑定
int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("bind error");
return 1;
}
cout << "bind" << endl;
//3.监听
res = listen(fd,4);
if(-1 == res) {
perror("listen error");
return 1;
}
cout << "listen" << endl;
//向多个客户端发送
vector<int> fds;
thread writer([&]() {
string s;
while(cin >> s) {
for(auto connfd:fds) {
write(connfd,s.c_str(),s.size()+1);
}
}
});
writer.detach();
for(;;) {
sockaddr_in remote_addr;
socklen_t len = sizeof(remote_addr);
//4.打开连接套接字
int connfd = accept(fd,(sockaddr*)&remote_addr,&len); //block
cout << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl; //打印哪个客户端连接服务器
if(-1 == connfd) {
perror("accept error");
return 1;
}
fds.push_back(connfd);
cout << "accept" << endl;
//5.读写数据
thread t([connfd]() {
for(;;) {
char buffer[256] = {0};
int n = read(connfd,buffer,256);
if(0 == n) {
cout << "client exit!" << endl;
break;
}
cout << buffer << endl;
}
});
t.detach();
}
//5.关闭套接字
for(auto connfd:fds) close(connfd);
close(fd);
}
[root@localhost 2]# ./sever 0.
argument error
Usage:./seversever_ip sever_port
[root@localhost 2]# ./sever 0.0.0.0 8080
bind
listen
127.0.0.1:33558
accept
1
127.0.0.1:42390
accept
3
127.0.0.1:54876
accept
hello
cccc
client exit!
client exit!
client exit!
...
[root@localhost 2]# ./client 127.0.0.1 8080
1
cccc
...
[root@localhost 2]# ./client 127.0.0.2 8080
3
cccc
...
[root@localhost 2]# ./client 127.0.0.8 8080
hello
cccc
结果分析:同时打开多个客户端,向服务器发送消息都能收到。服务器发送的消息每个客户端都能收到,且客户端连接和退出时,服务器都能打印相关信息。
2.4.4 实例4:客户端从服务器下载文件
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <thread>
#include <vector>
using namespace std;
int main(int argc,char* argv[]) {
if(4 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port path\n" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.绑定
int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("bind error");
return 1;
}
cout << "bind" << endl;
//3.监听
res = listen(fd,4);
if(-1 == res) {
perror("listen error");
return 1;
}
cout << "listen" << endl;
int connfd = accept(fd,NULL,NULL);
if(-1 == connfd){
perror("accept error");
return 1;
}
cout << "accept" << endl;
char buffer[256] = {0};
read(connfd,buffer,256);
cout << buffer << endl;
//请求文件的路径
string file = string(argv[3])+"/"+buffer;
cout << "require " << file.c_str() << endl;
int filefd = open(file.c_str(),O_RDONLY); if(-1 == filefd){
perror("open file error");
return 1;
}
struct stat s;
fstat(filefd,&s);
sendfile(connfd,filefd,0,s.st_size);
//5.关闭套接字
close(connfd);
close(fd);
}
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <thread>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.连接服务器
int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("connect error");
return 1;
}
//3.读写数据
string s;
cin >> s;
write(fd,s.c_str(),s.size()+1);
int pos = s.find_last_of('/');
//查询是否找到
if(pos != string::npos){ //找到了
s = s.substr(pos);
}
string file = "./" + s;
cout << file << endl;
int filefd = open(file.c_str(),O_CREAT|O_WRONLY,0666);
if(-1 == filefd){
perror("open file error");
return 1;
}
//下载文件
for(;;){
char buffer[256] = {0};
int n = read(fd,buffer,256);
write(filefd,buffer,n);
if(n < 256) break;
}
//4.关闭套接字
close(filefd);
close(fd);
}
流程:
开启服务器,开启客户端,输入下载文件名称
[root@localhost 2]# ./file_sever 0.0.0.0 8080 /root/Desktop/2021/C/c_chujie/1
bind
listen
accept
2.c
require /root/Desktop/2021/C/c_chujie/1/2.c
...
[root@localhost 2]# ./load 127.0.0.1 8080
2.c
./2.c
[root@localhost 2]# ll
total 152
-rw-r--r--. 1 root root 193 Jun 4 00:14 2.c
-rwxr-xr-x. 1 root root 28680 Jun 2 01:35 client
-rw-r--r--. 1 root root 1334 Jun 2 01:35 client2.cpp
-rw-r--r--. 1 root root 1242 Jun 1 23:33 client.cpp
-rwxr-xr-x. 1 root root 18568 Jun 4 00:12 file_sever
-rw-r--r--. 1 root root 1788 Jun 4 00:12 file_sever.cpp
-rwxr-xr-x. 1 root root 18336 Jun 3 23:55 load
-rw-r--r--. 1 root root 1582 Jun 3 23:55 load.cpp
-rwxr-xr-x. 1 root root 49608 Jun 2 01:34 sever
-rw-r--r--. 1 root root 2328 Jun 2 01:53 sever2.cpp
-rw-r--r--. 1 root root 1519 Jun 1 23:05 sever.cpp
2.4.5 实例5:多个客户端从服务器多次下载文件
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <thread>
#include <vector>
using namespace std;
int main(int argc,char* argv[]) {
if(4 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port path\n" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.绑定
int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("bind error");
return 1;
}
cout << "bind" << endl;
//3.监听
res = listen(fd,4);
if(-1 == res) {
perror("listen error");
return 1;
}
cout << "listen" << endl;
for(;;) {
int connfd = accept(fd,NULL,NULL);
if(-1 == connfd) {
perror("accept error");
return 1;
}
cout << "accept" << endl;
string cmd = string("ls -lR ")+argv[3];
FILE* pfile = popen(cmd.c_str(),"r");
if(NULL == pfile) {
perror("popen error");
return 1;
}
for(;;) {
char info[1025] = {0};
int n = fread(info,1,1024,pfile);
write(connfd,info,n);
if(n < 1024) break;
}
pclose(pfile);
thread t([=]() {
for(;;) {
char buffer[256] = {0};
int n = read(connfd,buffer,256);
if(n <= 0) break;
cout << buffer << endl;
//请求文件的路径
string file = string(argv[3])+"/"+buffer;
cout << "require " << file.c_str() << endl;
int filefd = open(file.c_str(),O_RDONLY);
if(-1 == filefd) {
perror("open file error");
return;
}
struct stat s;
fstat(filefd,&s);
sendfile(connfd,filefd,0,s.st_size);
}
close(connfd);
});
t.detach();
}
close(fd);
}
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <thread>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "sever_ip sever_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
//2.连接服务器
int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
if(-1 == res) {
perror("connect error");
return 1;
}
for(;;) {
char buffer[1024] = {0};
int n = read(fd,buffer,1024);
write(STDOUT_FILENO,buffer,n);
//cout << buffer;
//cout.flush();
if(n < 1024) break;
}
string s;
while(cin >> s) {
write(fd,s.c_str(),s.size()+1);
int pos = s.find_last_of('/');
//查询是否找到
if(pos != string::npos) { //找到了
s = s.substr(pos);
}
string file = "./" + s;
cout << file << endl;
int filefd = open(file.c_str(),O_CREAT|O_WRONLY,0666);
if(-1 == filefd) {
perror("open file error");
return 1;
}
//下载文件
for(;;) {
char buffer[256] = {0};
int n = read(fd,buffer,256);
write(filefd,buffer,n);
if(n < 256) break;
}
//4.关闭套接字
close(filefd);
}
close(fd);
}
结果分析:打开两个客户端分别从服务器下载文件
[root@localhost 2]# ./file_sever2 0.0.0.0 8080 /root/Desktop/2021/C/Test1
bind
listen
accept
2059.c
require /root/Desktop/2021/C/Test1/2059.c
accept
asx.c
require /root/Desktop/2021/C/Test1/asx.c
open file error: No such file or directory
accept
2093.c
require /root/Desktop/2021/C/Test1/2093.c
...
//客户端1
[root@localhost 2]# ./load2 127.0.0.1 8080
/root/Desktop/2021/C/Test1:
total 80
-rw-r--r--. 1 root root 280 Mar 8 2021 2059.c
-rw-r--r--. 1 root root 383 Mar 7 2021 2093.c
-rw-r--r--. 1 root root 261 Mar 9 2021 2111.c
-rw-r--r--. 1 root root 359 Mar 17 2021 2137.c
-rw-r--r--. 1 root root 195 Mar 4 2021 235.c
-rw-r--r--. 1 root root 152 Mar 8 2021 2405.c
-rw-r--r--. 1 root root 205 Mar 8 2021 2406.c
-rw-r--r--. 1 root root 169 Mar 8 2021 2407.c
-rw-r--r--. 1 root root 300 Mar 9 2021 2408.c
-rw-r--r--. 1 root root 259 Mar 8 2021 2415.c
-rw-r--r--. 1 root root 269 Mar 9 2021 2416.c
-rw-r--r--. 1 root root 229 Mar 8 2021 2417.c
-rw-r--r--. 1 root root 430 Mar 9 2021 2418.c
-rw-r--r--. 1 root root 348 Mar 9 2021 2419.c
-rwxr-xr-x. 1 root root 11136 Mar 17 2021 a.out
-rw-r--r--. 1 root root 177 Mar 4 2021 daoxu.c
-rw-r--r--. 1 root root 322 Mar 4 2021 rili.c
-rw-r--r--. 1 root root 286 Mar 8 2021 zsx.c
2059.c
./2059.c
...
//客户端2
[root@localhost 2]# ./load2 127.0.0.1 8080
/root/Desktop/2021/C/Test1:
total 80
-rw-r--r--. 1 root root 280 Mar 8 2021 2059.c
-rw-r--r--. 1 root root 383 Mar 7 2021 2093.c
-rw-r--r--. 1 root root 261 Mar 9 2021 2111.c
-rw-r--r--. 1 root root 359 Mar 17 2021 2137.c
-rw-r--r--. 1 root root 195 Mar 4 2021 235.c
-rw-r--r--. 1 root root 152 Mar 8 2021 2405.c
-rw-r--r--. 1 root root 205 Mar 8 2021 2406.c
-rw-r--r--. 1 root root 169 Mar 8 2021 2407.c
-rw-r--r--. 1 root root 300 Mar 9 2021 2408.c
-rw-r--r--. 1 root root 259 Mar 8 2021 2415.c
-rw-r--r--. 1 root root 269 Mar 9 2021 2416.c
-rw-r--r--. 1 root root 229 Mar 8 2021 2417.c
-rw-r--r--. 1 root root 430 Mar 9 2021 2418.c
-rw-r--r--. 1 root root 348 Mar 9 2021 2419.c
-rwxr-xr-x. 1 root root 11136 Mar 17 2021 a.out
-rw-r--r--. 1 root root 177 Mar 4 2021 daoxu.c
-rw-r--r--. 1 root root 322 Mar 4 2021 rili.c
-rw-r--r--. 1 root root 286 Mar 8 2021 zsx.c
2093.c
./2093.c
^C
[root@localhost 2]# ll
total 244
-rw-r--r--. 1 root root 280 Jun 4 01:40 2059.c
-rw-r--r--. 1 root root 383 Jun 4 01:41 2093.c
-rw-r--r--. 1 root root 0 Jun 4 01:41 asx.c
-rwxr-xr-x. 1 root root 28680 Jun 2 01:35 client
-rw-r--r--. 1 root root 1334 Jun 2 01:35 client2.cpp
-rw-r--r--. 1 root root 1242 Jun 1 23:33 client.cpp
-rwxr-xr-x. 1 root root 34104 Jun 4 01:03 file_sever
-rwxr-xr-x. 1 root root 34104 Jun 4 01:39 file_sever2
-rw-r--r--. 1 root root 2527 Jun 4 01:38 file_sever2.cpp
-rw-r--r--. 1 root root 2365 Jun 4 01:19 file_sever2.cpp.orig
-rw-r--r--. 1 root root 1788 Jun 4 00:46 file_sever.cpp
-rwxr-xr-x. 1 root root 18336 Jun 3 23:55 load
-rwxr-xr-x. 1 root root 18432 Jun 4 01:11 load2
-rw-r--r--. 1 root root 1908 Jun 4 01:36 load2.cpp
-rw-r--r--. 1 root root 1752 Jun 4 01:11 load2.cpp.orig
-rw-r--r--. 1 root root 1582 Jun 4 00:25 load.cpp
-rw-r--r--. 1 root root 0 Jun 4 01:19 s
-rwxr-xr-x. 1 root root 49608 Jun 2 01:34 sever
-rw-r--r--. 1 root root 2328 Jun 2 01:53 sever2.cpp
-rw-r--r--. 1 root root 1519 Jun 1 23:05 sever.cpp
2.5 UDP基本流程
2.5.1 单播
基本流程
connfd = socket(AF_INET,SOCK_DGRAM,0)
(2)设置发送地址和端口
struct sockaddr_in si;
si.sin_family = AF_INET; // 套接字地址簇,一般使用AF_INET
si.sin_port = htons(端口); // 16位端口,网络序
si.sin_addr.s_addr = inet_addr(IP地址); // IP地址,网络序
(3)发送数据
sendto(connfd,buf,buf_size,0,(struct sockaddr *)&si,sizeof(si));
(4)关闭socket
close(connfd);
int connfd = socket(AF_INET, SOCK_DGRAM, 0);
(2)设置接收地址和端口
struct sockaddr_in si;
si.sin_family = AF_INET; // 套接字地址簇,一般使用AF_INET
si.sin_port = htons(端口); // 16位端口,网络序
si.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY表示接收来自任意IP、任意网卡的发给指定端口的数据
(3)端口绑定
int ret = bind(connfd, (struct sockaddr *)&si, sizeof(si));
(4)接受数据
recv(connfd,buf,buf_size,0);
(5)关闭socket
close(connfd);
实例1:采用UDP实现单向发送信息
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "send_ip send_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
string s;
getline(cin,s);
sendto(fd,s.c_str(),s.size()+1,0,(sockaddr*)&addr,sizeof(addr));
close(fd);
}
include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "recv_ip recv_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
bind(fd,(sockaddr*)&addr,sizeof(addr));
char buffer[1024] = {0};
recv(fd,buffer,1023,0);
cout << buffer << endl;
close(fd);
}
[root@localhost udp]# ./send 0.0.0.0 8080
123
...
[root@localhost udp]# ./recv 127.0.0.1 8080
123
实例2:采用UDP实现双向发送信息
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "send_ip send_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
string s;
getline(cin,s);
sendto(fd,s.c_str(),s.size()+1,0,(sockaddr*)&addr,sizeof(addr));
char buffer[1024] = {0};
recv(fd,buffer,1023,0);
cout << buffer << endl;
close(fd);
}
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "recv_ip recv_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
bind(fd,(sockaddr*)&addr,sizeof(addr));
char buffer[1024] = {0};
//recv(fd,buffer,1023,0);
//cout << buffer << endl;
sockaddr_in remote_addr;
socklen_t len = sizeof(remote_addr);
recvfrom(fd,buffer,1023,0,(sockaddr*)&remote_addr,&len);
cout << buffer << endl;
string s;
getline(cin,s);
sendto(fd,s.c_str(),s.size()+1,0,(sockaddr*)&remote_addr,len);
close(fd);
}
[root@localhost udp]# ./recv2 0.0.0.0 8080
123
abc
...
[root@localhost udp]# ./send2 127.0.0.1 8080
123
abc
2.5.2 组播/多播
基本流程
(1)打开套接字
int socket(int domain, int type, int protocol); 返回套接字
(2)构建服务器地址结构
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP
serveraddr.sin_port = htons(SERVER_PORT);//端口
(3)绑定地址
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
(4)构建组播属性结构
struct ip_mreqn group;
inet_pton(AF_INET,GROUP,&group.imr_multiaddr);//设置组播地址
inet_pton(AF_INET,"0.0.0.0",&group.imr_address);//设置本地地址
group.imr_ifindex=if_nametoindex("ent0");//设置网卡接口
(5)设置组播权限和属性
setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&group,sizeof(group));//设置组播权限及选项
(6)设置客户端组播地址
struct sockaddr_in cliaddr;
bzero(&cliaddr,sizeof(cliaddr));
cliaddr.sin_family=AF_INET;
inet_pton(AF_INET,GROUP,&cliaddr.sin_addr.s_addr);
cliaddr.sin_port=htons(CLIENT_PORT);
(7)发送数据
sendto(sockfd,buf,strlen(buf),0,(structsockaddr*)&cliaddr, sizeof(cliaddr));//往组播地址发送信息,返回数据大小
(8)关闭套接字
close(fd);
实例:多播
-
send.cpp
同上
multirecv.cpp
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(4 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "recv_ip recv_port multi_ip" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
bind(fd,(sockaddr*)&addr,sizeof(addr));
struct ip_mreq group;
group.imr_multiaddr.s_addr = inet_addr(argv[3]);
group.imr_interface.s_addr = inet_addr(argv[1]);
if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0){
perror("Adding multicast group error");
close(fd);
return 1;
}
char buffer[1024] = {0};
recv(fd,buffer,1023,0);
cout << buffer << endl;
close(fd);
}
2.5.3 广播
基本流程
(1)打开socket
cfd = socket(AF_INET,SOCK_DGRAM,0) :
(2)打开广播
setsockopt(cfd,SOL_SOCKET,SO_BROADCAST,&n,sizeof(n));
参数n
: 0表示关闭属性,非0表示打开属性
(3)设置发送地址和端口
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(端口);
si.sin_addr.s_addr = inet_addr("255.255.255.255");
(4)发送数据
sendto(cfd,buffer,buffer_size,0,(struct sockaddr *)&si,sizeof(si));
(5)关闭socket
close(cfd);
与单播发送者一致
实例:广播
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
int main(int argc,char* argv[]) {
if(3 != argc) {
cout << "argument error" << endl;
cout << "Usage:" << argv[0] << "send_ip send_port" << endl;
return 1;
}
//1.打开套接字
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd) {
perror("open socket error");
return 1;
}
int opt = 1;
if(setsockopt(fd,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt))==-1) {
perror("setsockopt fail.");
close(fd);
return 1;
}
//编辑套接字信息
sockaddr_in addr;
addr.sin_family = AF_INET;
inet_aton(argv[1],&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址
addr.sin_port = htons(atoi(argv[2]));//addr的端口号 = 主机序转化为网络序
string s;
getline(cin,s);
sendto(fd,s.c_str(),s.size()+1,0,(sockaddr*)&addr,sizeof(addr));
close(fd);
}