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部分(还在更新中):

✨🎉总结

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

所以,

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

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

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

相关推荐
饮啦冰美式15 分钟前
22.04Ubuntu---ROS2使用rclcpp编写节点
linux·运维·ubuntu
wowocpp15 分钟前
ubuntu 22.04 server 安装 和 初始化 LTS
linux·运维·ubuntu
Huaqiwill17 分钟前
Ubuntun搭建并行计算环境
linux·云计算
wclass-zhengge19 分钟前
Netty篇(入门编程)
java·linux·服务器
Lign1731421 分钟前
ubuntu unrar解压 中文文件名异常问题解决
linux·运维·ubuntu
lulu_gh_yu23 分钟前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
方方怪25 分钟前
与IP网络规划相关的知识点
服务器·网络·tcp/ip
Re.不晚1 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
vip4511 小时前
Linux 经典面试八股文
linux
大霞上仙1 小时前
Ubuntu系统电脑没有WiFi适配器
linux·运维·电脑