主要函数:
TCP实现服务器与客户端的通信流程
//服务器端---服务器是一个被动的角色
1.socket //买一个手机
2.bind //SIM卡 绑定一个手机号(ip+port)
3.listen //待机(等待电话打入)
4.accept //接听电话
5.read/write //通话
6.close //挂机
//客户端---客户端是一个主动发起请求的一端
1.socket //买一个手机
2.bind(可选的) //SIM卡(绑定号码)
3.connect //拨打电话
4.read/write //通话
5.close //挂机
//1.socket ---- 插口
int socket(int domain, int type, int protocol);
功能: 创建通信的一端 (socket)
参数:
@domain //"域" --范围
AF_INET //IPV4 协议的通信
@type SOCK_STREAM //TCP (流式套接字)
@protocol 0 //LINUX下 流式套接字 ==>TCP
//协议
返回值:
成功 对应的socket文件描述符
失败 返回-1
注意:
文件描述符:
实际上就是 创建好的 socket的一个标识符
//2.bind
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:
给指定的 socket 绑定地址信息
参数:
@sockfd //表示操作的socket
@addr //填充的地址信息(ip + port)
@addrlen //地址信息结构体的大小
返回值:
成功 0
失败 -1
//通用的地址结构
struct sockaddr {
sa_family_t sa_family; //AF_INET //IPV4的协议
char sa_data[14];//(ip+port)
}
//网络通信的地址结构(internet)
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
//1.定义一个 地址结构体变量
struct sockaddr_in addr;
bzero(&addr,sizeof(addr)); //清0的函数
//2.之后进行信息填充
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//127.0.0.1 是回环测试的地址
//3.进行绑定
if(bind(sockfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
{
perror("bind fail");
return 0;
}
//3.listen --- 设置监听 ---作用:让操作系统监控是否有客户端发起连接
int listen(int sockfd, int backlog);
功能:
设置监听
参数:
@sockfd //监听套接字
@backlog //监听队列的大小
返回值
成功 0
失败 -1
listenfd
--------------监听队列------------------
fd1 fd2 fd3 fd4
|
-|--------------------------------------
|
\---->建立好连接的套接字
accept函数获取已连接的套接字 返回对应
的标识符
|--->后面的读写操作 都是通过这个标识符
进行的
-----------------------------------------------
accept(); //accept 从监听队列中获得已连接的的socket,返回一个标示符来表示已连接的socket
//后续通过已连接的socket进行通信
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能: 获取连接
参数:
@sockfd //监听套接字的fd(标识符)
@addr //来电显示(保存对端的地址信息)(ip+port)
@addrlen //表示 addr 参数对应类型的大小,值结果参数 --- 就是在用的时候,必须先赋一个初值,最后函数调用完成
//通过该参数,返回一个结果值
返回值:
成功 已连接的socket的标识符
失败 -1
//connect ---发起连接
int connect(
int sockfd, //表示 进行通信的 socket的标识符
const struct sockaddr *addr, //对端的地址信息(ip+port)
socklen_t addrlen); //表示的是 addr 参数类型的长度
参数:
@sockfd //通过socket函数获得的fd
@addr //服务器端的地址
@addrlen //参数addr类型的大小
//数据流向 fd --> buf (count 表示一次读取多少个字节)
ssize_t read(int fd, void *buf, size_t count);
//数据流向 buf--> fd (count 表示一次写多少个字节)
ssize_t write(int fd, const void *buf, size_t count);
参数:
@fd 就是要操作的 socket对应的 标示符
@buf 保存数据的一块内存首地址
@count 一次操作的字节数
confd
char buf[] = "hello QCXY\n";
write(confd,buf,strlen(buf)); //写 数据到socket中
//读数据出来
char rbuf[1024] = {0}; //表示申请了一块1024个字节大小
//的内存空间
read(confd,rbuf,sizeof(rbuf)); //读取数据
//练习:
实现 客户端 向服务器发送数据
服务器回发数据的功能
client ----- server
scanf(); ---(1)----> read 之后printf
read <--(2)---- write
printf
循环做,结束条件
当客户端输入 "quit"字符串时 客户端结束
怎么判断客户端读到的是"quit"
c语言中
"quit" == buf; (X) //不能这么写
//字符串的比较函数
strcmp("quit",buf);
strncmp("quit",buf,4);
客户端的程序:
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
//./client 127.0.0.1 8888
int main(int argc, const char *argv[])
{
int fd;
int ret = 0;
char buf[1024] = {0};
char rbuf[1024] = {0};
//处理命令行参数
//1.socket(手机)
//2.bind(电话卡)
//3.connect (拨打电话)
//处理命令行参数
if(argc != 3)
{
printf("Usage: %s\n",argv[0]);
return -1;
}
//1.socket(手机)
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0) //出错处理
{
perror("socket fail");
return -1;
}
printf("fd = %d\n",fd);
//2.bind(电话卡)---绑定的是客户端自己的地址信息
//客户端地址信息
//1.定义一个 地址结构体变量
struct sockaddr_in cli_addr;
bzero(&cli_addr,sizeof(cli_addr)); //清0的函数
//2.之后进行信息填充
cli_addr.sin_family = AF_INET;
cli_addr.sin_port = htons(7777);
cli_addr.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(fd,(struct sockaddr*)&cli_addr,sizeof(cli_addr)) < 0)
{
perror("bind fail");
return -1;
}
//服务器端的地址信息
//1.定义一个 地址结构体变量
struct sockaddr_in addr;
bzero(&addr,sizeof(addr)); //清0的函数
//2.之后进行信息填充
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.connect (拨打电话)
if(connect(fd,(struct sockaddr*)&addr,sizeof(addr))<0)
{
perror("connect fail");
return -1;
}
printf("connect success\n");
//通信过程
while(1)
{
//客户端从键盘获得数据
//数据流向stdin --> buf
fgets(buf,sizeof(buf),stdin); //stdin表示是从键盘获得数据
//发送给服务器
write(fd,buf,strlen(buf));
//接受服务器回发的消息
ret = read(fd,rbuf,sizeof(rbuf));
//如果回发的消息是
//quit
//则结束
rbuf[ret] = '\0';
printf("rbuf = %s\n",rbuf);
if(strncmp("quit",buf,4) == 0)
{
close(fd);
break;
}
}
return 0;
}
服务端
#include
#include /* See NOTES */
#include
#include
#include
#include
//./server 127.0.0.1 8888
int main(int argc, const char *argv[])
{
int fd = 0;
int connfd = 0;
int ret = 0;
char buf[1024] = {0};
//处理命令行参数
if(argc != 3)
{
printf("Usage: %s\n",argv[0]);
return -1;
}
//1.socket 创建套接字
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0) //出错处理
{
perror("socket fail");
return -1;
}
printf("fd = %d\n",fd);
//2.绑定
//1.准备地址信息
//2.绑定
//
//1.定义一个 地址结构体变量
struct sockaddr_in addr;
bzero(&addr,sizeof(addr)); //清0的函数
//2.之后进行信息填充
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
//127.0.0.1 是回环测试的地址
//3.进行绑定
if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
{
perror("bind fail");
return 0;
}
printf("bind success\n");
//4.设置监听
if(listen(fd,5) < 0)
{
perror("listen fail");
return -1;
}
struct sockaddr_in peer_addr;
socklen_t addrlen = sizeof(peer_addr);
//5.获取连接 -- 接听电话
while(1) //可以不断接受客户端的请求
{
//connfd = accept(fd,NULL,NULL);
connfd = accept(fd,(struct sockaddr*)&peer_addr,&addrlen);
if(connfd < 0)
{
perror("accept fail");
return -1;
}
printf("connfd = %d\n",connfd);
printf("-----------------------\n");
printf("ip = %s\n",inet_ntoa(peer_addr.sin_addr));
printf("port = %d\n",ntohs(peer_addr.sin_port));
printf("-----------------------\n");
//通信过程
//效果实现数据回发
while(1)
{
//read 与 write 的返回值 大于0 时表示的是
//一次成功操作到的字节数
ret = read(connfd,buf,sizeof(buf));
//hello
buf[ret] = '\0'; //添加'\0'--转换成字符串
printf("buf = %s\n",buf);//字符串打印 需要
//结束标志 '\0'
if(ret == 0 || strncmp(buf,"quit",4) == 0)
{
close(connfd);
break;
}
write(connfd,buf,ret);
}
} //telnet
return 0;
}
补充:
可以用如下函数替代read,write
ssize_t recv(int sockfd, void* buf,size_t len,int flags);
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
@sockfd //进行操作的socket的文件描述符
@buf //保存数据的首地址
@len //一次操作的字节数
@flags //标志0--默认的操作方式(阻塞)
返回值:
成功 成功操作的字节数
失败 -1&errno
全部0条评论
快来发表一下你的评论吧 !