网络编程2——CS模型的TCP通信流程总结 及 server、client的实现
一、socket模型创建流程图
二、server的实现
1,报错文件先写好,基本头文件写好,C的、网络的
2,创建socket,定义文件描述符lfd,记得要返回值检查
3,创建bind函数,其中第二个参数是服务器端地址结构,要定义及初始化并在传参时要强制转换
4,创建listen函数,传入文件描述符和允许同时接入的个数
5,创建accept函数,第二个参数是传入的已连接的客户端地址结构,因此也要定义一个客户端地址结构(不用初始化,因为是传进来的),传入时同样要强制转换,man查看手册的时候会发现accept函数的第三个参数是一个socklen_t
类型的客户地址结构长度,因此也要定义一个clit_addr_len
,accept函数返回值也记得检查
6,accept成功之后就开始等待连接,连接成功就可以从缓冲区中读取read待处理字符串,进行操作并写回write缓冲区
7,重要的最后一步,别忘记close
文件描述符
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<ctype.h>
#define SERV_PORT 9527//定义一个端口号
void sys_err(const char * str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int lfd = 0;
int cfd = 0;//accept返回的用于创建连接的socket
int ret;//read会返回读到的实际字节数
char buf[BUFSIZ];//read、write需要缓冲区,从读写到缓冲区的,一般用一个系统自带宏定义缓冲区大小,4096
struct sockaddr_in serv_addr;//定义服务器端的地址结构
struct sockaddr_in clit_addr;//定义客户端的地址结构,在accept的时候要做参数(其实可以和上一行一起写,但是第一次写还是清楚点吧
socklen_t clit_addr_len;//定义客户端传入传出参数,accept的第三个参数,socklen_t 这是个类型,man accept中的参数类型不要弄错
//初始化地址结构
serv_addr.sin_family = AF_INET;//跟socket的第一个参数一样,表示IPv4还是6
serv_addr.sin_port =htons(SERV_PORT); //不能直接放宏,要讲本地字节序转换成网络字节序
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET, SOCK_STREAM,0);
if(lfd == -1)//函数返回值一定要检查
{
sys_err("socket error");
}
//创建成功就得到一个文件描述符,接下来就是bind,给socket绑定地址结构
//bing(文件描述符,地质结构的指针,地址结构长度)
bind(lfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr));//注意第二个参数 地址结构的强转
//bind之后listen建立最大同时通信数,再accept阻塞客户端建立连接
listen(lfd, 128);
clit_addr_len = sizeof(clit_addr);
cfd = accept(lfd,(struct sockaddr*)(&clit_addr),&clit_addr_len)//注意第三个参数是传入传出参数,因此首先要有个传入值,accept会返回一个新的文件描述符
if(cfd ==-1)
{
sys_err("accept error");
}
//创建成功,服务器就开始从客户端读内容(此处为小写字母)
while(1){
ret = read(cfd, buf,sizeof(buf));//读到buf中,ret就是实际读到的字节数
write(STDOUT_FILED, buf, ret);//写到屏幕上,不然看不见
for(int i = 0; i<ret; ++i)
{
buf[i] = toupper(buf[i]);//注意用到一个新函数就要去看看该函数的头文件ctype.h
write(cfd, buf, ret);
}//这就转换完毕,再给写回到buf中
}
close(lfd);
close(cfd);
}
写个makefile调试一下,没错就可以启动服务器./server
,启动了服务器就开始阻塞等待连接,此时我们还没有写客户端,可以先用nc
命令试一下 nc = net connectnc IP地址 端口号(此处是9527)
光标闪烁即开始通信,就可以输入一串小写字母看是否会返回大写字符串
三、获取客户端地址结构
客户端跟服务器成功建立连接可以得到客户端建立连接的IP地址和端口号,因为accept函数的第二个参数就是客户端的地址结构,因此我们可以得到客户端信息:用IP地址转换函数得到IP地址,通过转换字节序函数得到端口号(ntohs
)√
网络字节序和本地字节序的转换,点分十进制是本地字节序
inet_ntop():输入是地址结构得到ip地址,网络转换成本地
inet_pton():输入是IP地址得到网络字节序形式,本地转换成网络
四、client的实现
1,建立客户端socket,一个cfd接收,并检查返回值
2,客户端不用bind,直接就可以connect,man手册查看connect参数列表
3,connect函数的第二个参数时服务器地址结构,因此要定义(已知的)服务端地址结构,serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
//服务器端口inet_pton(AF_INET,"127.0.0.1",&ser_addr.sin_addr.s_addr);
//用IP地址转换函数得到网络字节序的服务器ip地址
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define SERV_PORT 9527//服务器端口号
void sys_err(const char* str)
{
perror(str);
exit(1);
}
int main()
{
int cfd;
int ret;//connect要有一个接收值
int counter = 10;
char buf[BUFSIZ];//read的时候是从缓冲区中读的,默认BUFSIZ大小
struct sockaddr_in serv_addr;//服务器地址结构
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&ser_addr.sin_addr.s_addr);
cfd = socket(AF_INET, SOCK_STREAM,0);//
if(cfd ==-1)
sys_err("socket error");
ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));//注意参数类型,是否需要强制转换。成功返回0
if(ret != 0 )
sys_err("connect err");
//连接成功,就可以往socket写数据
while(--counter)
{
write(cfd, "hello",5);
//服务器会有返回值
ret = read(cfd, buf,sizeof(buf));//读到buf中,ret是读到的字节数
write(STDOUT_FILED,buf,ret);
}
close(cfd);
return 0;
}
放到同一个目录下,起服务器,起客户端即可
五、TCP通信流程(假设是实现大小写转换)
server端:
1,socket() 创建socket
2,bind() 绑定服务器地址结构
3,listen() 设置监听上限
4,accept() 阻塞监听客户端连接
5,read(fd)读socket获取客户端数据
6,小写—>大写 toupper()
7,write(fd)
8,close()
client:
1,socket() 创建socket
2,connect() 与服务器建立连接
3,write() 写数据到socket
4,read() 读转换后的数据
5,显示读取的结果
6,close()
上一篇: muduo库分析——base篇(9)日志