TCP客户端/服务器端通信模型
由于个人手拙画不出较好的图案,所以借助以前教科书中的一个图案来描述tcp通信的过程:
TCP服务器端代码实现
/**********************************************
*TCP 服务器端实现步骤
*(1)使用socket()函数创建套接字
*(2)为创建的套接字绑定到指定的地址结构
*(3)listen()函数设置套接字为监听模式,使服务器进入被动打开状态;
*(4)接受客户端的链接请求,建立连接
*(5)接受、应答客户端的数据请求
*(6)终止链接
*
*************************************************/
//定义头文件
#include <stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 1234 //定义使用的端口号
#define BACKLOG 1 //定义最大链接数
void main(){
int listenfd,connectfd; //socket文件描述符
struct sockaddr_in server; //服务器地址信息描述符
struct sockaddr_in client; //客户端地址信息描述符
socklen_t addrlen;
/******************************************************************
*功能说明:调用socket函数产生套接字描述符
*函 数:int socket(int family,int type,int protocol)
*参数说明:family 指明协议簇,确定socket使用的协议类型,其值常为:
* (1)AF_INET :IPV4协议
* (2)AF_INET6:IPV6协议
* (3)AF_ROUTE:路由套接口
* type 指明产生套接字的类型,其值常为:
(1)SOCK_STREAM:字节流套接口,TCP使用的是这种形式
(2)SOCK_DGRAME:数据报套接口,UDP使用的是这种形式
(3)SOCK_RAW:原始套接口
Protocol 协议标志,一般在调用socket函数时将其置为0,当如果是原始套接字,就需要为protocol指定一个常值
*返回值说明:如果函数调用失败返回 “-1”,如果调用成功,将返回一个小的非负的整数值
*********************************************************************/
if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("socket() error.");
exit(1);
}
/*****************************************************
*功能说明:设置套接字选项SO_REUSEADDR,即地址重用选项,此处这两行不可少!
*如果缺少则程序运行时会产生错误信息“Bind() error:Address already in use”
*******************************************************/
int opt=SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
/*******************************************************
*功能说明:初始化服务器的地址结构,并为地址结构赋值
*函数说明:extern void bzero(void *s,int n);
*参数说明:s 要置零的数据的起始地址
* n 要置零的数据字节个数
*******************************************************/
bzero(&server,sizeof(server)); //将套接字地址结构server设置初始值为0
server.sin_family=AF_INET; //设置使用的协议簇类型
server.sin_port=htons(PORT); //设置使用的端口,并且将主机字节序转换为网络字节序
server.sin_addr.s_addr=htonl(INADDR_ANY); //对于IPV4统配地址由常数INADDR_ANY常数来指定,其值一般为0,它通知内核选择IP地址
/*******************************************************
*功能说明:将套接字和指定的协议地址绑定
*函数说明:int bind(int sockfd,const struct sockaddr *server,socklen_len addrlen);
*参数说明:sockfd 套接字函数返回的套接字描述符
* server 指向特定于协议的地址结构的指针,指定用于通信的本地协议地址
* addrlen 指定了该套接字地址结构的长度
*返回值说明:调用成功返回0,出错返回-1,并置错误号errno
*******************************************************/
if(bind(listenfd,(struct sockaddr *)&server,sizeof(server))==-1){
perror("Bind() error");
exit(1);
}
/*********************************************************
*功能说明:将套接字描述符转换成监听描述符,等待客户的连接
*函数说明:int listen(int sockfd,int backlog);
*参数说明:sockfd要设置的描述符
* backlog规定了请求队列中的最大连接个数
*返回值说明:函数调用成功返回0,出错返回-1,并置errno值
**********************************************************/
if(listen(listenfd,BACKLOG)==-1){
perror("listen() error.\n");
exit(1);
}
int len=sizeof(client);
/******************************************
*功能说明:接受客户链接,客户的地址信息存放在client地址结构中
*函数说明:int accept(int listenfd,struct sockaddr *clinet,socklen_t * addrlen);
*参数说明:listenfd参数是由socket函数产生的套接字描述符,在调用accept函数前,已经调用listen函数将次套接字变成了监听套接字
* client 返回连接对方的套接字地址结构
* addrlen 返回链接对方的套接字对应的结构长度
*返回值说明:函数调用失败返回-1,至于调用成功参考P30
*******************************************/
if((connectfd=accept(listenfd,(struct sockaddr *)&client,&addrlen))==-1){
perror("accept() error\n");
exit(1);
}
printf("You got a connetcion from cient's ip is %s,port is %d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));
send(connectfd,"Welcome\n",8,0); //向客户端发送欢迎信息
close(connectfd); //关闭链接
close(listenfd); //关闭监听状态
}
TCP客户端代码实现
/*****************************************************************
* TCP客户端实现的步骤:
*
*(1)使用socket()函数创建套接字
*(2)调用connect函数建立一个与TCP服务器的链接
*(3)发送数据请求,接收服务器的数据应答
*(4)终止链接
*******************************************************************/
//头文件包含
#include <stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
//宏定义
#define PORT 1234 //定义端口
#define MAXDATASIZE 100 //定义缓冲区大小
/********************************
*在main函数中有两个参数:
*argc 指命令行中参数的个数
*argv[] 指向字符串
********************************/
int main(int argc,char *argv[]){
int sockfd,num; //sockfd是sock file descriptor的缩写,是套接字函数返回的套接字描述符
char buf[MAXDATASIZE]; //定义用于存储数据的缓冲区的大小
struct hostent *he; //定义指向hostent结构体的指针
struct sockaddr_in server; //定义结构体变量sockaddr_in变量
/*************************************************
* 检查用户的输入,如果输入不正确则提示错误信息
**************************************************/
if (argc!=2){
printf("Usage:%s<IP Address>\n",argv[0]);
exit(1);
}
/***********************************************************
*功能说明:通过用户输入的点分十进制形式的IP地址信息获取服务器的地址信息
*函 数:struct hostent * gethostbyname(const char * hostname)
*参 数:hostname 是主机的域名地址
*功 能:函数将查询的结果作为参数返回,如果失败返回空指针,如果成功此参
* 数返回非空指针指向hostent结构
* hostent的结构实例:
* struct hostent{
* char * h_name; //主机的正式名称
* char **h_aliases; //主机的别名列表
* int h_addrtype; //主机地址类型
* int h_length; //主机地址长度
* char **h_addr_list; //主机IP地址的列表
* }
************************************************************/
if((he=gethostbyname(argv[1]))==NULL){
printf("gethostbyname() error\n");
exit(1);
}
/******************************************************************
*功能说明:调用socket函数产生套接字描述符
*函 数:int socket(int family,int type,int protocol)
*参数说明:family 指明协议簇,确定socket使用的协议类型,其值常为:
* (1)AF_INET :IPV4协议
* (2)AF_INET6:IPV6协议
* (3)AF_ROUTE:路由套接口
* type 指明产生套接字的类型,其值常为:
(1)SOCK_STREAM:字节流套接口,TCP使用的是这种形式
(2)SOCK_DGRAME:数据报套接口,UDP使用的是这种形式
(3)SOCK_RAW:原始套接口
Protocol 协议标志,一般在调用socket函数时将其置为0,当如果是原始套接字,就需要为protocol指定一个常值
*返回值说明:如果函数调用失败返回 “-1”,如果调用成功,将返回一个小的非负的整数值
*********************************************************************/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
printf("socket() error\n");
exit(1);
}
/*******************************************************
*功能说明:初始化服务器的地址结构,并为地址结构赋值
*函数说明:extern void bzero(void *s,int n);
*参数说明:s 要置零的数据的起始地址
* n 要置零的数据字节个数
*******************************************************/
bzero(&server,sizeof(server)); //将套接字地址结构server设置初始值为0
server.sin_family=AF_INET; //设置使用的协议簇类型
server.sin_port=htons(PORT); //设置使用的端口,并且将主机字节序转换为网络字节序
server.sin_addr=*((struct in_addr *)he->h_addr);
/****************************************************
*函数说明:建立与服务器之间的链接
*函数说明:int connect(int sockfd,const struct sockaddr * addr,socklen_t addrlen)
*参数说明: sockfd socket函数返回的套接字描述符
* addr 指向服务器的套接字地址结构的指针
* addrlen 该套接字地址结构的大小
*******************************************************/
if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))==-1){
printf("connect() error\n");
exit(1);
}
/******************************************************
*功能说明:接受服务器端发来的字符串
*函数说明:ssize_t recv(int sockfd,void *buf,size_t len,int flags);
*参数说明:sockfd 套接字描述符
* buf 指向一个用于接收信息的数据缓冲区
* len 指明接收数据缓冲区的大小
* flag 传输控制标识符,其值定义如下:
* (1)0 常规操作 (常用,其余的借鉴书籍P32)
******************************************************/
if((num==recv(sockfd,buf,MAXDATASIZE,0))==-1){
printf("recv() error\n");
exit(1);
}
buf[num-1]='\0';
printf("server message:%s\n",buf);
close(sockfd);
}
运行实例
服务器端:
客户端: