系统调用和应用编程接口
在进行tcp通信之前,我们先铺垫一下系统调用和应用编程接口。
操作系统在使用系统调用时将控制权
在应用程序
和操作系统
之间传递。如下图:
当某个应用进程启动系统调用时,控制权就从应用进程传递给系统调用接口。此接口再把控制权传递给计算机操作系统。那么接下来操作系统内部会执行请求的操作。内部一旦执行完成,就把控制权交给系统调用接口,接下来系统调用接口就会把控制权交给应用进程。我们可以总结如下:系统调用接口实际上就是应用进程的控制权和操作系统的控制权进行转换的一个接口。这个系统调用的接口又称之为应用编程接口—API.
在我们要实现的tcp通信中将套接字—socket作为接口
接下来用一张图来表示网络通信时的系统调用
可以看到在在套接字以上的进程是受应用进程控制的,而在套接字以下的运输层协议则受操作系统控制。因此,在网络通信中必须通过套接字与系统交互。当应用进程需要使用网络通信时,必须先发出socket系统调用,请求创建“套接字”。其实这个的效果就是请求操作系统把网络通信所需的一些资源分配给应用进程。当通信完毕后,应当close—关闭套接字,通知操作系统回收该套接字的所有资源。(至于套接字的详细介绍,这里不再详细讲述,自己可以去深入探索)
接下里就要实现通信功能了
最简单的TCP网络程序
server.c 的作⽤用是接受client的请求,并与client进⾏行简单的数据通信,整体为⼀一个阻塞式的网络聊天⼯工具。
代码如下:
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
#include<error.h>
#include<netinet/in.h>
const char* ip = "127.0.0.1";
const int port =8080;
const int back_log=5;
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 1;
}
struct sockaddr_in seraddr;
struct sockaddr_in cliaddr;
seraddr.sin_family = AF_INET;
// inet_pton(AF_INET,ip,&seraddr.sin_addr);
seraddr.sin_addr.s_addr = inet_addr(ip);
seraddr.sin_port = htons(port);
if( bind(sock,(struct sockaddr*)&seraddr,sizeof(struct sockaddr_in)) < 0 )
{
printf("encounter an error when bind\n");
close(sock);
return 2;
}
if( listen(sock,back_log) < 0 )
{
printf("listen error\n");
close(sock);
return 3;
}
while(1)
{
int cli_len = sizeof(cliaddr);
int fd = accept(sock,(struct sockaddr*)&cliaddr,&cli_len);
if(fd < 0)
{
printf("accept error\n");
close(sock);
return 4;
}
char ip_buf[100];
memset(ip_buf,'\0',sizeof(ip_buf));
inet_ntop(AF_INET,&cliaddr.sin_addr,ip_buf,sizeof(ip_buf));
int _port = ntohs(cliaddr.sin_port);
printf("get connect,ip is:%s,port is %d\n",ip_buf,_port);
while(1)
{
char buf[1024];
memset(buf,'\0',sizeof(buf));
read(fd,buf,sizeof(buf));
printf("client said #:%s\n",buf);
printf("server said $ :");
memset(buf,'\0',sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = '\0';
write(fd,buf,strlen(buf)+1);
printf("please wait ...\n");
}
}
close(sock);
return 0;
}
client.c的作⽤用是链接server,并向server发起通信请求。
代码如下:
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
#include<error.h>
#include<netinet/in.h>
#include<arpa/inet.h>
//const char* ip = "127.0.0.1";
//const int port = 8080;
int main(int argc, char* argv[])
{
if(argc != 3 )
{
printf("%d enter ip and port \n",argc);
return 1;
}
char buf[1024];
memset(buf,'\0',sizeof(buf));
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 1;
}
struct sockaddr_in server_sock;
bzero(&server_sock,sizeof(server_sock));
server_sock.sin_family = AF_INET;
server_sock.sin_addr.s_addr = inet_addr(argv[1]);
//server_sock.sin_addr.s_addr = inet_addr(ip);
//inet_pton(AF_INET,ip,&server_sock.sin_addr);
server_sock.sin_port = htons(atoi(argv[2]));
//server_sock.sin_port = htons(port);
int ret = connect(sock,(struct sockaddr*)&server_sock,sizeof(server_sock));
if(ret < 0 )
{
printf("connect failed ...\n");
close(sock);
return 2;
}
printf("connect success...\n");
while(1)
{
printf("client said #:");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = '\0';
write(sock,buf,sizeof(buf));
if(strncasecmp(buf,"quit",4) == 0)
{
printf("quit\n");
break;
}
printf("please wait...\n");
read(sock,buf,sizeof(buf));
printf("server said $:%s\n",buf);
}
close(sock);
return 0;
}
Makefile代码如下:
.PHONY:all
all:client server
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f server client
代码其实很简单,那接下来我对整个连接的整个过程总结如下:
- 建立连接阶段
当套接字被创建后,它的端口号和IP地址都是空的,因此应用进程需要调用bind ()来指明套接字的本地地址。服务器端就是server端,把熟知或者port和ip填写进套接字当中,客户端则可以绑定也可以不必绑定。服务器端在调用bind()后必须调用listen ()监听客户端请求。紧跟着再调用accept (),来及时接受客户端发来的请求。 - 数据传送阶段
在这里我用到了管道通信的方法。服务器从accept()返回后⽴立刻调 用read(),读socket就像读管道⼀一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送 请求给服务器,服务器收到后从read()返回,对客户端的请求进⾏行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞 等待下⼀一条请求,客户端收到后从read()返回,发送下⼀一条请求,如此循环下去。 - 释放连接
一旦客户或服务器结束使用套接字,就把套接字撤销。调用close释放连接和取消套接字。