Linux学习之网络编程2(socket,简单C/S模型)
写在前面
Linux网络编程我是看视频学的,Linux网络编程,看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。
网络字节序
- 小端法:pc本地存储,高位存高地址,低位存低地址。
- 大端法:网络存储,高位存低地址,低位存高地址。
由此我们看到本地和网络的存储方式不一样,所以每次建立连接都要转换,下面我来介绍一些关于大端法和小端法的转换函数。
htonl
:本地——>网络,转换的是IPhtons
:本地——>网络,转换的是端口ntohl
:网络——>本地,转换的是IPntohs
:网络——>本地,转换的是端口
其实这四个函数非常好记,
h
代表host
表示本地,n
代表network
表示网络,l
代表long
存的IP,s
代表short
存的是端口。(所以说学好英语还是很重要的)
IP转换函数
int inet_pton(int af, const char *src, void *dst);
功能:本地字节序(string IP)——> 网络字节序。
参数:
af
:AF_INET
或者AF_INET6
src
:传入参数,IP地址(点分十进制)dst
:传出参数,转换后的网络字节序。
返回值:- 成功,1
- 异常:0,说明
src
指向的不是一个有效的IP地址。 - 失败,-1
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
功能:网络字节序——>本地字节序(string IP)
参数:
af
:AF_INET
或者AF_INET6
src
: 网络字节序IP地址dst
:本地字节序(string IP)size
:dst
的大小。
返回值:- 成功,
dst
- 失败,NULL
Socket套接字
套接字概念
在通信过程中,套接字一定是 成对出现。
一个文件描述符指向一个套接字(该套接字内部由借助内核缓冲区实现读写)
网络通信的流程
- 服务器端:
- 先用
socket()
生成一个套接字lfd
用来监听 - 用
bind()
对第一步生成的套接字绑定地址结构(绑的是服务器的地址结构) - 用
listen()
函数设置lfd
的监听上限,最大是128. - 用
accept()
阻塞直到有客户端请求连接。 - 处理请求,得到客户端的地址结构,和用于通信的套接字
cfd
(这个套接字是调用accept()
后返回值) - 成功建立连接,进行通信,处理业务逻辑。
- 先用
- 客户端:
- 先用
socket()
生成一个套接字sfd
- 使用
connect()
请求与服务器建立连接 - 成功建立连接,进行通信,处理业务逻辑。
- 先用
相关函数介绍
socket函数
语法:
int socket(int domain, int type, int protocol);
功能:
创建一个套接字
参数:
domain
:AF_INET
或AF_INET6
type
:数据传输协议,SOCK_STREAM
(表示用流式协议,使用TCP通信传这个参数)或SOCK_DGRAM
(表示用报式协议,使用UDP通信传这个参数)。(后面用本地套接字通信还会学到SOCK_LOCAL
,这个后面学到再介绍)protocol
:默认传0,表示让系统自动根据type
来选择,SOCK_STREAM
的代表协议是TCP
,SOCK_DGRAM
的是UDP
返回值:
- 成功,新套接字所对应的文件描述符
- 失败:-1 ,error
bind函数
语法:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
给socket
绑地址结构
参数
sockfd
:socket
函数的返回值,要绑定的套接字addr
:传入参数,要绑定的地址结构。addrlen
:sizeof(addr)
地址结构的大小
addr
类型是一个结构体,上面写的是struct sockaddr
结构,实际我们创建的时候要创建struct sockaddr_in
,它里面有三个成员变量。
sin_family
:和当时创建套件字的第一个参数一样,传AF_INET
sin_port
:绑定的端口号,只不过要转换成网络字节序sin_addr.s_addr
:这个是最复杂的,sin_addr
本身又是一个结构体,但里面只有一个成员s_addr
,所以就直接拿出来了。传的是绑定的IP地址,同样也要转换。下面给个example,最后传的时候要类型强转一下
返回值:
- 成功,0
- 失败,-1
listen函数
语法:
int listen(int sockfd, int backlog);
功能:
设置同时与服务器建立连接的客户端数量的上限
参数:
sockfd
:要监听的套接字backlog
:上限值,最大是128
返回值:
- 成功,0
- 失败,-1
accept函数
语法:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
阻塞等待客户端建立连接
参数
sockfd
:监听的套接字addr
:传出参数,表示建立连接的客户端的地址结构(IP+端口)addrlen
:传入传出参数。入:addr
的大小,出:客户端addr
的实际大小
返回值
- 成功,能与服务器进行数据通信的套接字的文件描述符,即服务器用这个便可与客户端
- 失败,-1
connect函数
语法:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
使用客户端现有的套接字与服务器建立连接
参数:
sockfd
:客户端自己的套接字addr
:服务器的地址结构addrlen
:服务器地址结构的长度
返回值:
- 成功,0
- 失败,-1
demo
说明
我们来写一个小demo,实现的功能是:客户端与服务器建立连接后,服务器可以将客户端发送的小写字母变成大写然后发送回去
服务器源代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>
#define PORT 6666
void sys_err(char * str)
{
perror(str);
exit(1);
}
int main()
{
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1)
sys_err("socker error");
struct sockaddr_in addr_ser,addr_cli;
addr_ser.sin_family=AF_INET;
addr_ser.sin_port=htons(PORT);
addr_ser.sin_addr.s_addr=htonl(INADDR_ANY);
int res=bind(sfd,(struct sockaddr *)&addr_ser,sizeof addr_ser);
if(res==-1)
sys_err("bind error");
res=listen(sfd,128);
if(res==-1)
sys_err("listen error");
socklen_t addr_cli_len=sizeof addr_cli;
int cfd=accept(sfd,(struct sockaddr*)&addr_cli,&addr_cli_len);
if(res==-1)
sys_err("accept error");
char client_IP[1024];
printf("client IP is %s,port is %d\n",inet_ntop(AF_INET,&addr_cli.sin_addr.s_addr,&client_IP,sizeof client_IP),ntohs(addr_cli.sin_port));
while(1)
{
char buf[BUFSIZ];
int n=read(cfd,buf,sizeof buf);
write(STDOUT_FILENO,buf,n);
for(int i=0;i<n;i++)
buf[i]=toupper(buf[i]);
write(cfd,buf,n);
}
return 0;
}
客户端源代码
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<arpa/inet.h>
#define PORT 6666
void sys_err(char* str)
{
perror(str);
exit(1);
}
int main()
{
int cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1)
sys_err("socket error");
struct sockaddr_in addr_ser;
addr_ser.sin_family=AF_INET;
addr_ser.sin_port=htons(PORT);
inet_pton(AF_INET,"127.0.0.1",&addr_ser.sin_addr.s_addr);
int res=connect(cfd,(struct sockaddr*)&addr_ser,sizeof addr_ser);
if(res==-1)
sys_err("connect error");
while(1)
{
char buf[BUFSIZ];
int n=read(STDIN_FILENO,buf,sizeof buf);
write(cfd,buf,n);
read(cfd,buf,n);
write(STDOUT_FILENO,buf,n);
}
return 0;
}
效果展示
写好服务器后,可以再打开一个终端先用命令
nc IP port
l来测试服务器,命令里的IP是服务器的实际IP,port是服务器实际的端口
写在最后
个人亲身经验:我们学习的一系列Linux命令,一定要自己亲手去敲。不要只是看别人敲代码,不要只是停留在眼睛看,脑袋以为自己懂了,等你实际上手去敲会发现许许多多的这样那样的问题。毕竟“实践出真知”。
如果你觉得我写的题解还不错的,请各位王子公主移步到我的其他题解看看
- 数据结构与算法部分(还在更新中):
- C++ STL总结 - 基于算法竞赛(强力推荐)
- 动态规划——01背包问题
- 动态规划——完全背包问题
- 动态规划——多重背包问题
- 动态规划——分组背包问题
- 动态规划——最长上升子序列(LIS)
- 二叉树的中序遍历(三种方法)
- 最长回文子串
- 最短路算法——Dijkstra(C++实现)
- 最短路算法———Bellman_Ford算法(C++实现)
- 最短路算法———SPFA算法(C++实现)
- 最小生成树算法———prim算法(C++实现)
- 最小生成树算法———Kruskal算法(C++实现)
- 染色法判断二分图(C++实现)
- Linux部分(还在更新中):
✨🎉总结
“种一颗树最好的是十年前,其次就是现在”
所以,
“让我们一起努力吧,去奔赴更高更远的山海”
如果有错误❌,欢迎指正哟😋
🎉如果觉得收获满满,可以动动小手,点点赞👍,支持一下哟🎉
更多推荐
所有评论(0)