利用套接字实现一个简单的TCP服务器客户端模型基本步骤如下:
1.创建套接字
#include
#include
int socket(int domain, int type, int protocol);
参数描述:
domian:协议域,AF_INET 对应 ipv4,AF_INET6 对应 ipv6, AF_UNIX 表示这个socket是非网络形式的unix域,可以用来进行非网络形式的进程间通信。
type:指定socket类型
1.SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。 这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
2.SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
3.SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
返回值:
成功返回socket描述符,失败返回-1
2.将服务器IP与端口和套接字进行绑定
#include
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数描述:
sockfd:通过socket()函数创建,用来表示唯一一个socket
addr:指向要绑定给sockfd的协议地址(协议类型,IP,端口号)
addrlen:对应地址长度
返回值:
成功返回0,失败返回-1
注意:
在IPv4因特网域AF_INET中,套接字地址用结构sockaddr_in表示,如下:
struct sockaddr_in
{
sa_family_t sin_family; //unsigned short 地址族
in_port_t sin_sport; //uint16_t
struct in_addr sin_addr; //IPv4
};
struct in_addr
{
in_addr_t s_addr; //uint32_t
3.监听请求
#include
#include
int listen(int sockfd, int backlog);
参数描述:
sockfd:要监听的套接字
backlog:连接队列长度
backlog 具体一些是什么意思呢?每一个连入请求都要进入一个连入请求队列,等待listen 的程序调用accept()函数来接受这个连接。当系统还没有调用accept()函数的时候,如果有很多连接,那么本地能够等待的最大数目就是backlog 的数值。
返回值:
成功返回0,失败返回-1
4.服务器端接受监听到的请求
当服务器端进行监听到一个连接请求时,就可以使用accept()函数来接受这个请求。
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数描述:
socket:服务器的套接字
addr:用于存储客户端的协议地址(协议类型,IP,端口号)
addrlen:地址长度(sizeof(addr))
返回值:
成功返回一个新的套接字描述符,代表与返回客户端的TCP连接
5.客户端连接服务器
#include
#include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数描述:
sockfd:客户端的sockfd
addr:用于存储服务器的协议地址(协议类型,IP,端口号)
addrlen:地址长度(sizeof(addr))
返回值:
成功返回0,失败返回-1
注意:
在实现过程中还应注意网络字节序的问题,主机的字节序是不确定的,有的使用大端存储,有的使用小端存储,为了不使网络通信因为机器之间存储模式而造成错误,TCP/IP协议规定网络数据流使用大端字节序。
下面库函数可以完成主机序列和为网络序列的转换:
//主机序列到网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
//网络序列到主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
server:
#include
#include
#include
#include
#include
#include
#include
static void Usage(char* proc)
{
printf("Usage: %s [local_ip] [local_port]\n", proc);
}
int startup(char* ip, int port)
{
//1.创建文件特性套接字
//AF_INET ipv4,SOCK_STREAM,基于字节流服务
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return 2;
}
printf("sock = %d\n", sock);
struct sockaddr_in local;
//确定地址协议类型
local.sin_family = AF_INET;
//绑定端口
local.sin_port = htons(port);
//绑定ip
//这里的IP是点分十进制,使用inet_addr()可以将其转换成二进制
local.sin_addr.s_addr = inet_addr(ip);
//2.绑定网络特性将套接字与服务器ip和端口号绑定
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
perror("bind");
return 3;
}
//3.监听请求
if(listen(sock, 10) < 0)
{
perror("listen");
return 4;
}
return sock;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
//获取套接字
int listen_sock = startup(argv[1], atoi(argv[2]));
while(1)
{
//用于存储连接的客户端地址信息(地址类型,IP,端口号)
struct sockaddr_in client;
socklen_t len = sizeof(client);
//4.接受监听到的连接
int new_sock = accept(listen_sock,(struct sockaddr*)&client, &len);
if(new_sock < 0)
{
perror("accept");
return 5;
}
while(1)
{
char buf[1024];
//从套接字读取信息到buf中
ssize_t s = read(new_sock, buf, sizeof(buf) - 1);
if(s > 0)
{
buf[s] = 0;
printf("client# %s\n", buf);
printf("Please Enter# ");
fflush(stdout);
//从键盘输入信息到buf中
ssize_t _s = read(0, buf, sizeof(buf)-1);
buf[_s-1] = 0;
//发送信息到套接字
write(new_sock, buf, strlen(buf));
}
else if(s == 0)
{
printf("client colse !!\n");
return 6;
}
else
{
perror("read");
return 7;
}
}
}
return 0;
}
client:
#include
#include
#include
#include
#include
#include
static void Usage(char *proc)
{
printf("Usage %s [server_ip] [server_port]\n", proc);
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
//创建套接字
int sock = socket(AF_INET, SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
//将创建的套接字,连入指定的网络服务中,这里连到了服务器
if(connect(sock,(struct sockaddr*)&server, sizeof(server)) < 0)
{
perror("connect111");
return 3;
}
char buf[1024];
while(1)
{
printf("Please Enter# ");
fflush(stdout);
//从键盘写入内容到缓冲区
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s >0)
{
buf[s-1] = 0;
printf("server# ");
fflush(stdout);
//将缓冲区内容通过套接字发送到服务器
write(sock, buf, strlen(buf));
//在从套接字中读取服务器的回应信息
ssize_t _s = read(sock, buf, sizeof(buf)-1);
if(_s > 0)
{
buf[_s] = 0;
printf("%s\n", buf);
}
if(s < 0)
{
perror("read");
return 4;
}
}
}
return 0;
}