C/S模型-TCP
server.c
socket() 建立套接字
bind() 绑定IP 端口号 (struct sockaddr_in addr 初始化)
listen() 指定最大同时发起连接数
accept() 阻塞等待客户端发起连接
read()
小–大
write 给 客户端
close();
client.c
socket();
bind(); 可以依赖“隐式绑定”
connect();发起连接
write();
read();
close();
服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
数据传输的过程:
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。
在学习socket API时要注意应用程序和TCP协议层是如何交互的: 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段
server
下面通过最简单的客户端/服务器程序的实例来学习socket API。
注意:本程序没有加入错误处理,后面会封装好加上
server.c的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端。
/*************************************************************************
> File Name: serverad.c
> Author: sunxingying
> Mail: [email protected]
> Created Time: 2017年02月25日 星期四 05时31分20秒
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 6666
int main()
{
int sfd,cfd;
int len,i;
char buf[1024],clie_IP[1024];
struct sockaddr_in serv_addr,clie_addr;
socklen_t clie_addr_len;
/*创建一个socket指定IPV4协议族 TCP协议*/
sfd=socket(AF_INET,SOCK_STREAM,0);
/*初始化一个地址结构 man 7 ip查看对应信息*/
//将整个结构体清零
bzero(&serv_addr,sizeof(serv_addr));
//选择协议族为IPV4
serv_addr.sin_family=AF_INET;
//监听本地所有IP地址
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//绑定端口号
serv_addr.sin_port=htons(SERV_PORT);
//绑定服务器地址结构
bind(sfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
//设定链接上限,注意此处不阻塞,同一时刻允许向服务器发起链接请求的数量
listen(sfd,64);
printf("wait for client connect....\n");
//获取客户端地址结构大小
clie_addr_len=sizeof(clie_addr_len);
//参数1是sfd;参数2传出参数,参数3传入传出参数,全部是client端的参数
//成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
//监听客户端链接会阻塞
cfd=accept(sfd,(struct sockaddr *)&clie_addr,&clie_addr_len);
printf("主机IP:%s\n 主机端口号%d\n",inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,clie_IP,sizeof(clie_IP)),ntohs(clie_addr.sin_port));
while(1)
{
//读取客户端发送数据
len=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,len);
//处理客户端数据
for(i=0;itoupper(buf[i]);
}
//处理完数据回写给客户端
write(cfd,buf,len);
}
//记得要关闭文件
close(sfd);
close(cfd);
return 0;
}
client
client.c的作用是从命令行参数中获得一个字符串发给服务器,然后接收服务器返回的字符串并打印。
/*************************************************************************
> File Name: client.c
> Author: sunxingying
> Mail: [email protected]
> Created Time: 2017年05月24日 星期三 05时43分20秒
************************************************************************/
#include
#include
#include
#include
#include
#include
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
int main(void)
{
int cfd;
char buf[1024];
struct sockaddr_in serv_addr;
socklen_t serv_addr_len;
cfd=socket(AF_INET,SOCK_STREAM,0);
int n;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);
inet_pton(AF_INET,SERV_IP,&serv_addr.sin_addr.s_addr);
connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
while(1)
{
fgets(buf,sizeof(buf),stdin);
write(cfd,buf,strlen(buf));
n=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,n);
}
close(cfd);
return 0;
}
运行结果如下:
socket模型