TCP客户端服务器模型的通信原理图
一、服务端
1、socket函数
功能:为通信创建一个端点,并返回该端点对应的文件描述符,文件描述符的使用原则是最小未分配原则。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数1:协议族,常用的协议族如下
|
Name
| Purpose | Man page |
|
AF_UNIX, AF_LOCAL
|
本地通信,同一主机的多进程通信
|
查看 man 7 unix
|
|
AF_INET
|
提供IPv4的相关通信方式
|
查看 man 7 ip
|
|
AF_INET6
|
提供IPv6的相关通信方式
|
查看 man 7 ipv6
|
参数2:通信类型,指定通信语义,常用的通信类型如下
|
SOCK_STREAM
|
支持TCP面向连接的通信协议
|
|
SOCK_DGRAM
|
支持UDP面向无连接的通信协议
|
参数3:通信协议,当参数2中明确指定特定协议时,参数3可以设置为0,但是有多个协议共同使用 时,需要用参数3指定当前套接字确定的协议。
返回值:成功返回创建的端点对应的文件描述符,失败返回-1并置位错误码
2、bind函数
功能:为套接字分配名称,给套接字绑定ip地址和端口号
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数1:要被绑定的套接字文件描述符
参数2:通用地址信息结构体,对于不同的通信域而言,使用的实际结构体是不同的,该结构体的目
的是为了强制类型转换,防止警告
通信域为:AF_INET而言,ipv4的通信方式
struct sockaddr_in {
sa_family_t sin_family; /* 地址族: AF_INET */
in_port_t sin_port; /* 端口号的网络字节序 */
struct in_addr sin_addr; /* 网络地址 */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* ip地址的网络字节序 */
};
通信域为:AF_UNIX而言,本地通信
struct sockaddr_un {
sa_family_t sun_family; /* 通信域:AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* 通信使用的文件 */
};
参数3:参数2的大小,可以用sizeof计算
返回值:成功返回0,失败返回-1并置位错误码
3、listen函数
功能:将套接字设置成被动监听状态
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数1:套接字文件描述符
参数2:挂起队列能够增长的最大长度,一般为128
返回值:成功返回0,失败返回-1并置位错误码
4、accept函数
功能:阻塞等待客户端的连接请求,如果已连接队列中有客户端,则从连接队列中拿取第一个,并创建一个用于通信的套接字
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数1:服务器套接字文件描述符
参数2:通用地址信息结构体,用于接受已连接的客户端套接字地址信息的
参数3:接收参数2的大小
返回值:成功发那会一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码
5、recv、send数据收发
(1)recv函数
功能:从套接字中读取消息放入到buf中
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数1:通信的套接字文件描述符
参数2:要存放数据的起始地址
参数3:读取的数据的大小
参数4:读取标识位,是否阻塞读取
0:表示阻塞等待
MSG_DONTWAIT:非阻塞
返回值:可以是大于0:表示成功读取的字节个数
可以是等于0:表示对端已经下线(针对于TCP通信)
-1:失败,并置位错误码
(2)send函数
功能:向套接字文件描述符中将buf这个容器中的内容写入
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数1:通信的套接字文件描述符
参数2:要发送的数据的起始地址
参数3:发送的数据的大小
参数4:读取标识位,是否阻塞读取
0:表示阻塞等待
MSG_DONTWAIT:非阻塞
返回值:成功返回发送字节的个数
-1:失败,并置位错误码
6、close函数
功能:关闭套接字文件描述符
#include <unistd.h>
int close(int fd);
参数:要关闭的套接字文件描述符
返回值:成功返回0,失败返回-1并置位错误码
7、服务器端代码实现
#include<myhead.h>
#define SER_IP "192.168.137.138"
#define SER_PORT 8888
int main(int argc, const char *argv[]){
//1、创建用于连接的套接字文件描述符
//参数1:AF_INET表示使用的是ipv4的通信协议
//参数2:SOCK_STREAM表示使用的是tcp通信
//参数3:由于参数2指定了协议,参数3填0即可
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1){
perror("socker error");
return -1;
}
printf("socket sfd = %d\n",sfd);
//2、绑定ip地址和端口号
//2.1 填充要绑定的ip地址和端口号结构体
sockaddr_in sin;
sin.sin_family=AF_INET; //通信域
sin.sin_port=htons(SER_PORT); //转换为网络字节序的端口号
sin.sin_addr.s_addr=inet_addr(SER_IP); //转换为网络字节序的ip
//2.2 绑定工作
//参数1:要被绑定的套接字文件描述符
//参数2:要绑定的地址信息结构体,需要进行强制类型转换,防止警告
//参数3:参数2的大小
if(bind(sfd,(sockaddr*)&sin,sizeof(sin))==-1){
perror("bind error");
return -1;
}
printf("bind success\n");
//3、启动监听
//参数1:要启动监听的文件描述符
//参数2:挂起队列的长度
if(listen(sfd,128)==-1){
perror("listen error");
return -1;
}
printf("listen success\n");
sockaddr_in scin;
socklen_t len=sizeof(scin);
//4、阻塞等待客户端的连接请求
int newfd=accept(sfd,(sockaddr*)&scin,&len);
if(newfd==-1){
perror("accept error");
return -1;
}
printf("[%s,%d]:connection successful\n",\
inet_ntoa(scin.sin_addr),ntohs(scin.sin_port));
//5、数据收发
char buf[128];
while(1){
bzero(&buf,sizeof(buf)); //将buf数组中清零
int res=recv(newfd,buf,sizeof(buf),0); //从客户端读取数据到buf
if(res==0){ //此时说明客户端下线
printf("[%s,%d]:leave\n",\
inet_ntoa(scin.sin_addr),ntohs(scin.sin_port));
break;
}
printf("[%s,%d]:%s\n",\
inet_ntoa(scin.sin_addr),ntohs(scin.sin_port),buf);
strcat(buf,"*_*");
if(send(newfd,buf,strlen(buf),0)==-1){ //将数据从buf中发送到客户端
perror("send error");
return -1;
}
printf("send sussessful\n");
}
//6、关闭套接字文件描述符
close(newfd);
close(sfd);
return 0;
}
二、客户端
其他函数均在上文有介绍,这里不过多赘述
1、connect 函数
功能:将指定的套接字,连接到给定的地址上
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数1:要连接的套接字文件描述符
参数2:通用地址信息结构体
参数3:参数2的大小
返回值:成功返回0,失败返回-1并置位错误码
2、客户端代码实现
注意:客户端中绑定ip地址和端口号并不是必要项,如果不绑定,系统会自动分配端口号。
#include<myhead.h>
#define SER_IP "192.168.137.138"
#define CLI_IP "192.168.137.138"
#define SER_PORT 8888
#define CLI_PORT 9999
int main(int argc, const char *argv[]){
//1、创建用于通信的客户端套接字描述符
int cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1){
perror("socket error");
return -1;
}
//2、绑定ip地址和端口号(可选)
//2.1 填充要绑定的地址信息结构体
sockaddr_in cin;
cin.sin_family=AF_INET;
cin.sin_addr.s_addr=inet_addr(CLI_IP);
cin.sin_port=htons(CLI_PORT);
//2.2 绑定工作
if(bind(cfd,(sockaddr*)&cin,sizeof(cin))==-1){
perror("bind error");
return -1;
}
//3、连接服务器
//3.1 填充要连接的服务器的地址信息结构体
sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_addr.s_addr=inet_addr(SER_IP);
sin.sin_port=htons(SER_PORT);
//3.2 连接工作
if(connect(cfd,(sockaddr*)&sin,sizeof(sin))==-1){
perror("connect error");
return -1;
}
//4、数据收发
char buf[128];
while(1){
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
if(send(cfd,buf,strlen(buf),0)==-1){
perror("send error");
return -1;
}
if(recv(cfd,buf,sizeof(buf),0)==-1){
perror("recv error");
return -1;
}
printf("receive message:%s\n");
}
//5、关闭描述符
close(cfd);
return 0;
}
C++基于TCP的网络通信实现

2533

被折叠的 条评论
为什么被折叠?



