Linux学习之网络编程2(socket,简单C/S模型)

写在前面

Linux网络编程我是看视频学的,Linux网络编程,看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。


网络字节序

  • 小端法:pc本地存储,高位存高地址,低位存低地址。
  • 大端法:网络存储,高位存低地址,低位存高地址。

由此我们看到本地和网络的存储方式不一样,所以每次建立连接都要转换,下面我来介绍一些关于大端法和小端法的转换函数。

  • htonl:本地------>网络,转换的是IP
  • htons:本地------>网络,转换的是端口
  • ntohl:网络------>本地,转换的是IP
  • ntohs:网络------>本地,转换的是端口

其实这四个函数非常好记,h代表host表示本地n代表network表示网络l代表long存的IPs代表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)
  • sizedst的大小。
    返回值:
  • 成功,dst
  • 失败,NULL

Socket套接字

套接字概念

在通信过程中,套接字一定是 成对出现。

一个文件描述符指向一个套接字(该套接字内部由借助内核缓冲区实现读写)

网络通信的流程

  • 服务器端:
    1. 先用socket()生成一个套接字lfd用来监听
    2. bind()对第一步生成的套接字绑定地址结构(绑的是服务器的地址结构)
    3. listen()函数设置lfd的监听上限,最大是128.
    4. accept()阻塞直到有客户端请求连接。
    5. 处理请求,得到客户端的地址结构,和用于通信的套接字cfd(这个套接字是调用accept()后返回值)
    6. 成功建立连接,进行通信,处理业务逻辑。
  • 客户端:
    1. 先用socket() 生成一个套接字sfd
    2. 使用connect()请求与服务器建立连接
    3. 成功建立连接,进行通信,处理业务逻辑。

相关函数介绍

socket函数

语法:

int socket(int domain, int type, int protocol);

功能:

创建一个套接字

参数:
  • domain:AF_INETAF_INET6
  • type:数据传输协议,SOCK_STREAM(表示用流式协议,使用TCP通信传这个参数)或SOCK_DGRAM(表示用报式协议,使用UDP通信传这个参数)。(后面用本地套接字通信还会学到SOCK_LOCAL,这个后面学到再介绍)
  • protocol:默认传0,表示让系统自动根据type来选择,SOCK_STREAM的代表协议是TCPSOCK_DGRAM的是UDP
返回值:
  • 成功,新套接字所对应的文件描述符
  • 失败:-1 ,error

bind函数

语法:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:

socket绑地址结构

参数
  • sockfd:socket函数的返回值,要绑定的套接字
  • addr:传入参数,要绑定的地址结构。
  • addrlensizeof(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,实现的功能是:客户端与服务器建立连接后,服务器可以将客户端发送的小写字母变成大写然后发送回去

服务器源代码

c 复制代码
#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;
}

客户端源代码

c 复制代码
#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 portl来测试服务器,命令里的IP是服务器的实际IP,port是服务器实际的端口


写在最后

个人亲身经验:我们学习的一系列Linux命令,一定要自己亲手去敲 。不要只是看别人敲代码,不要只是停留在眼睛看,脑袋以为自己懂了,等你实际上手去敲会发现许许多多的这样那样的问题。毕竟"实践出真知"。


如果你觉得我写的题解还不错的,请各位王子公主移步到我的其他题解看看

  1. 数据结构与算法部分(还在更新中):
  1. Linux部分(还在更新中):

✨🎉总结

"种一颗树最好的是十年前,其次就是现在"

所以,

"让我们一起努力吧,去奔赴更高更远的山海"

如果有错误❌,欢迎指正哟😋

🎉如果觉得收获满满,可以动动小手,点点赞👍,支持一下哟🎉

相关推荐
weixin_456732591 分钟前
网络-内核是如何与用户进程交互
网络·交互
多多*3 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
爱吃涮毛肚的肥肥(暂时吃不了版)29 分钟前
计算机网络34——Windows内存管理
网络·计算机网络·udp
李小星同志39 分钟前
高级算法设计与分析 学习笔记6 B树
笔记·学习
霜晨月c1 小时前
MFC 使用细节
笔记·学习·mfc
王哲晓1 小时前
Linux通过yum安装Docker
java·linux·docker
小江湖19941 小时前
元数据保护者,Caesium压缩不丢重要信息
运维·学习·软件需求·改行学it
gopher95111 小时前
linux驱动开发-中断子系统
linux·运维·驱动开发
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
码哝小鱼1 小时前
firewalld封禁IP或IP段
linux·网络