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;
}
三.自我总结
今天正式开始了网络编程部分,将之前学的理论知识可以实践真的很开心,希望自己能够用网络做出来一个可以拿的出手的东西。