Linux网络编程(一)---------客户端和服务端的实现

一.知识点梳理

1.网络字节序

1.1 大小端的定义

  • 大端:低地址,高字节 ,网络数据流采用大端字节序
  • 小端:低地址,低字节,常用的PC机通常为小端自序

1.2.网络字节序和主机字节序的转换

  • 下面的一些库函数可以进行相应的转化
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);	//将主机字节序转换为网络字节序
uint16_t htons(uint16_t hostshort);	//将主机字节序转换为网络字节序
uint32_t ntohl(uint32_t netlong);	//将网络字节序转换为主机字节序
uint16_t ntohs(uint16_t netshort);	//将网络字节序转换为网络字节序

大端与大端进行通信的时候不需要进行大小端之间的转换

1.3 socket地址的数据类型及相关函数

  • 有远古时代的struct sockaddr 和各种协议的struct sockaddr_in[IPV4、IPV6], struct sockaddr_un[UNIX下常用的]

在这里插入图片描述

  • 字符串转in_addr的函数
#include <arpa/inet.h>
//常用函数
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family, const char *strptr, void *addrptr);
  • in_addr转字符串的函数
char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family, const void *addrptr, char
*strptr, size_t len);

2. TCP协议的网络程序

2.1 TCP协议通讯流程

在这里插入图片描述

  • 连接建立过程

1.服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待客户端进行连接
2.客户端调用socket()初始化后,调用connect()发出SYN段阻塞等待服务器的应答
3.服务器向客户端应答一个SYN-ACK段
4.客户端收到后从connect()返回
5.服务器收到后从accept()返回

  • 数据传输过程【TCP提供的是全双工模式】

1.服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调
用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理
2.在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调
用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

  • 断开连接过程

1.客户端调用close()关闭连接
2.服务器端读到close,返回0,服务器端也关闭;

2.2 常用的socket API

  • 常用的socket API,这些函数都在sys/socket.h
  • socket()函数
int socket(int family, int type, int protocol);
//family表示协议簇
//type参数,TCP协议下指定为SOCK_STREAM, UDP协议下指定为SOCK_DGRAM
//protocol指定为0
  • bind()函数
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
//struct sockaddr *是一个通用指针类型
//myaddr参数实际上可以接受多种协议的sockaddr结构体
//而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度

作用:将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。

  • bzero函数
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;		
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);		
//网络地址为INADDR_ANY,这个宏表示本地的任意IP地址
servaddr.sin_port = htons(SERV_PORT);

作用:将整个结构体清零

  • listen()函数
int listen(int sockfd, int backlog);//backlog表示最多能连接的客户端的数量

作用:监听是否有客户端来连接服务器

  • accept()函数
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);	
//cliaddr是一个传出参数,accept()返回时传出客户端的地址和端口号。
//addrlen参数是一个传入传出参数(value-result argument)
//传入的是调用者提供的缓冲区cliaddr的长度以避免缓冲区溢出问题,
//传出的是客户端地址结构体的实际长度
  • connect()函数
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);	//连接的应答

二. 实现一个简单的客户端和服务端

1.服务端

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <ctype.h>
#include <netinet/in.h>

#define MAX_LEN		128		//定义最大字节数
#define PORT		6666	//定义服务端口号



int main(void){
	//初始化一些需要用的结构体变量和常量
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAX_LEN], str[INET_ADDRSTRLEN];
	int n, i;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);		//调用socket函数返回一个描述符

	bzero(&servaddr, sizeof(servaddr));				//结构体清零
	servaddr.sin_family = AF_INET;					//采用ipv4歇息
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);	//本地所有网卡的地址
	servaddr.sin_port = htons(PORT);				//端口号

	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));		//将servaddr与listenfd进行绑定

	listen(listenfd, 5);							//设置最大连接数为5,并进行监听客户端
	printf("等待客户机连接.............\n");

	while (1){
		cliaddr_len = sizeof(cliaddr);				//对客户地址长度的初始化

		connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);		//返回客户机的ip地址和端口号
		memset(buf, 0, MAX_LEN);						//接受数据的数组置为0
		n = read(connfd, buf, MAX_LEN);					//读取客户端发送的数据

		printf("从IP地址为%s,端口号为%d接收数据为:\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));		//读取和打印客户端传来的数据
		printf("%s\n", buf);

		for (int i = 0; i < n; i++){		//将小写转为大写
			buf[i] = toupper(buf[i]);
		}
		write(connfd, buf, n);		发送给客户端

	}
	close(connfd);		//关闭当前的连接
	return 0;
}

2. 客户端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define MAX_LEN		128			//定义最大字节数
#define SERV_PORT	6666		//定义服务端口号
#define IP_ADDR		"127.0.0.1"			//将本机的地址设为本机地址

int main(int argc, char **argv){
	//初始化一些需要用的结构体变量和常量
	struct sockaddr_in servaddr;
	char buf[MAX_LEN];
	int sockfd, n;
	/*
	if (argc != 2){
		fputs("./client message", stderr);
		exit(1);
	}*/
	char str[MAX_LEN];
	while (1){

	sockfd = socket(AF_INET, SOCK_STREAM, 0);	//调用socket函数返回一个描述符

	bzero(&servaddr, sizeof(servaddr));			//结构体清零
	servaddr.sin_family = AF_INET;				//采用ipv4歇息
	servaddr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, IP_ADDR, &servaddr.sin_addr);		

	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));		//连接并进行与服务器绑定
	memset(str, 0, MAX_LEN);

	printf("请输入你要转换成大写的字符串:\n");
	scanf("%s", str);

	write(sockfd, str, strlen(str));		//向服务器发送数据
	n = read(sockfd, buf, MAX_LEN);			//读取服务器发来的数据

	printf("客户机回应为:\n");
	printf("%s\n", buf);	
	close(sockfd);				//断开连接

	}

	return 0;
}

三.自我总结

今天正式开始了网络编程部分,将之前学的理论知识可以实践真的很开心,希望自己能够用网络做出来一个可以拿的出手的东西。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值