TCP客户端与服务器的通信模型:
下面介绍图中的函数(API):
- socket函数:创建套接字,指定期望的协议类型,成功返回套接字描述符
- connect函数:TCP客户端使用connect函数建立与TCP服务器端的连接(三次握手)
- bind函数:命名套接字,把一个本地协议地址赋予套接字
- listen函数:TCP服务器使用listen函数监听新的客户连接,需要创建一个监听队列存放待处理的客户连接,默认值为5
- accept函数:TCP服务器端使用accept函数从listen监听队列中接受一个连接
- close函数:用来关闭套接字,终止TCP连接( 四次挥手 )
实现完整的TCP服务器端客户端程序(单客户机访问)
服务器端
服务器端编程流程:
- socket:创建一个socket,用函数socket();
- bind :绑定IP地址、端口等信息到socket上,用函数bind();
- listen:开启监听,用函数listen();
- accept:接收客户端发来的连接,用函数accept();
- recv / send:收发数据,用函数send()和recv(),或者read()和write();
- close:关闭网络连接(套接字)
下面是代码实现服务器端编程流程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
int main()
{
//1.创建套接字
int socked = socket(AF_INEF,SOCK_STREAM,0);
assert(sockfd != -1);
//定义服务器和客户端的专用socket地址
struct sockaddr_in saddr , caddr;
memset(&saddr,0,sizeof(saddr));
//设置地址族、端口号、ip地址
saddr . sin_family = AF_INET;
saddr . sin_port = htons(6000);
saddr .sin_addr . s_addr = int_addr("192.168.31.114");
//2.通过bind将套接字与socket地址绑定起来
int res = bind(socked,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
//3.创建监听队列,监听队列长度设为5
listen(sockfd,5);
while(1)
{
int len = sizeof(caddr);
//4.accept监听socket,若存在,则从监听队列中接受一个连接
函数返回一个链接套接字,否则,没有新连接请求,则阻塞等待
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
//accept失败返回-1表示没有获取到连接,继续循环
if(c<0)
{
continue;
}
printf("accept c =%d\n",c);
//5.recv读取链接套接字connfd缓冲区buffer的内容
成功返回实际读取到的数据的长度,flag置0使用默认
char buff[128] = {0};
recv(c, buff,127,0);
printf("buff=%s\n",buff);
//6、send向链接套接字connfd上写入数据,flag置0使用默认
send(c,"OK",2,0);
//7,关闭套接字
close(c);
}
close(sockfd);
}
使用下述命令查看与TCP相关的网络套接字连接情况:
netstat -antp
相关参数:
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服務状态
-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。
提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到
接下来我们完成一个客户端的编写,实现客户端与服务器端的通信。
客户端
客户端编程流程:
- socket:创建一个socket,用函数socket();
- 设置要连接的服务器端的IP地址和端口等属性;
- connect:连接服务器,用函数connect();
- recv / send:收发数据,用函数send()和recv(),或者read()和write();
- close:关闭网络连接(套接字)
下面代码实现客户端编程流程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
int main()
{
/* 1、socket函数创建套接字 */
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
mimeses(&ser,0,sizeof(ser));
/* 设置要连接的服务器端的IP地址和端口等属性 */
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = int_addr("127.0.0.1");
//2、connect函数与服务器端三次握手建立连接,建立成功后,
sockfd就唯一地标识这个连接,客户端就可以通过读写sockfd
来与服务器进行数据通信
int res = connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
while(1)
{
printf("please input:");
fflush(stdout);
char buff[128] = {0};
fgets(buff,128,stdin);
buff[strlen(buff) - 1] = 0;
//客户端发送bye,即标识客户端要终止连接
if(strcmp(buff,"end") == 0)
{
break;
}
//3、send向sockfd即写入数据,即向服务器发送数据,flag位默认
send(sockfd,buff,strlen(buff),0);
char recvbuff[128] = {0};
//4、recv从sockfd读入数据,即读取服务器回复的数据,flag位默认
recv(sockfd,buff,127,0);
printf("%s\n",buff);
}
close(sockfd);
}
TCP数据读写:
对文件的读写操作read和write同样适用于socket。
但是socket编程接口也提供几个专门的数据读写的系统调用,增加对数据的读写控制。
#include <sys/types.h>
#include <sys/socket.h>
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);
socket的描述符对应一个打开的文件,可以往文件里写入或者读取文件的数据。但是使用其专门的读写接口更加的合适同时可以设定缓冲区提高读写的效率
flages参数为数据收发提供了额外的控制。