TCP服务器搭建流程
1.创建套接字
int socket(int domain, int type, int protocol);
{
domain:指定通信域;这将选择用于通信的协议族。一般选择AF_INET (IPv4) 、AF_INET6(IPv6) ;
type: 指定的类型,该类型指定通信语义。一般使用SOCK_STREAM(流式套接字,适用于TCP)、SOCK_DGRAM(数据报套接字,一般适用于UDP)、 SOCK_RAW(原始套接字);
protocol:指定套接字类型,与 type确定选用的类型 , 0:默认type选取的类型。
}
2.绑定套接字
struct sockaddr_in {
sa_family_t sin_family; /* 通信协议族: AF_INET */
in_port_t sin_port; /* 设置端口 */
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr; /* 设置IP地址 */
};int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
{
sockfd:通信套接字
addr:(struct sockaddr_in*)结构体地址,类型不一样,需要强转
addrlen:(struct sockaddr_in *)结构体大小
}
这里为什么要用struct sockaddr_in,而不是直接用struct sockaddr?
因为struct sockaddr中sa_data把目标地址和端口信息混在了一起,而sockaddr_in解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,所以为了方便直接用struct sockaddr_in,用时强转一下就可以了。
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
3.监听套接字
int listen(int sockfd, int backlog);
{
sockfd:监听套接字
backlog :同时能够监听的最多客户端个数,不表示同时能够连接的客户端个数(0-1024)
}
4.接受客户端连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
{sockfd:监听套接字
addr:获取并存放客户端地址信息,如果不关心则传NULL
addrlen:存放客户端地址信息,如果不关心则传NULL
}
//accept()自身有阻塞效果,阻塞等待客户端连接,并且还会创建一个通信套接字listfd,这个通信套接字属于这个客户端与服务器之间独有,后续适用通信套接字来完成数据交互
5.数据接收/发送
ssize_t read(int fd, void *buf, size_t count);//接收
ssize_t write(int fd, const void *buf, size_t count);//发送
{
fd:通信套接字listfd
buf:数组地址,存放接收/发送的数据
count:每次接收的大小
}
6.关闭套接字
int close(int fd);
{
fd:创建的通信套接字套接字和监听套接字
}
//回收资源,避免占用内存
TCP客户端搭建流程
1:创建套接字
int socket(int domain, int type, int protocol);
{
domain:指定通信域;这将选择用于通信的协议族。一般选择AF_INET (IPv4) 、AF_INET6(IPv6) ;
type: 指定的类型,该类型指定通信语义。一般使用SOCK_STREAM(流式套接字,适用于TCP)、SOCK_DGRAM(数据报套接字,一般适用于UDP)、 SOCK_RAW(原始套接字);
protocol:指定套接字类型,与 type确定选用的类型 , 0:默认type选取的类型。
}
2:主动连接请求
struct sockaddr_in {
sa_family_t sin_family; /* 通信协议族: AF_INET */
in_port_t sin_port; /* 服务器设置的端口号 */
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr; /* 服务器的IP地址 */
};int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
{
sockfd:监听套接字
addr:(struct sockaddr_in *)结构体地址,类型不一样,需要强转
addrlen:(struct sockaddr _in*)结构体大小
}
3:数据接收/发送
ssize_t read(int fd, void *buf, size_t count);//接收
ssize_t write(int fd, const void *buf, size_t count);//发送
{
fd:监听套接字
buf:数组地址,存放接收/发送的数据
count:每次接收的大小
}
4:关闭套接字
int close(int fd);
{
fd:监听套接字
}
TCP服务器
tcp_sever.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#define SIZE 128
#define BACKLOG 5
#define PERROR(msg) do{perror(msg);exit(-1);}while(0)
int main(int argc,char *argv[])
{
int comfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(comfd < 0)
PERROR("socket");
struct sockaddr_in sev,cli; //定义结构体
sev.sin_family = AF_INET; //选择协议族
sev.sin_port = htons(8080); //设置端口,htons():主机转为网络字节序
sev.sin_addr.s_addr = inet_addr("0.0.0.0"); //自动获取
//inet_addr()地址转换函数,将点分十进制二进制
if(bind(comfd,(struct sockaddr *)&sev,sizeof(sev)) < 0) //绑定套接字
PERROR("bind");
if(listen(comfd,BACKLOG) < 0) //监听套接字
PERROR("listen");
int listfd;
memset(&cli,0,sizeof(cli)); //清空
int cli_len = sizeof(cli);
//服务器循环
while(1)
{
printf("listen----------\n");
listfd = accept(comfd,(struct sockaddr *)&cli,&cli_len); //接收客户端连接请求
if(listfd == 0)
PERROR("accept");
printf("[%s]$##[%d]\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
//客户端IP,端口
//inet_ntoa将二进制转为点分十进制,ntohs网络字节序转为主机字节序
char buf[SIZE] = {0};
while(1)
{
if(read(listfd,buf,sizeof(buf)) == 0) //判断客户端是否断开连接
{
printf("client quit ---------\n");
break;
}
printf("buf:%s\n",buf);
write(listfd,"xka",4); //给客户端回复消息
}
close(listfd); //关闭套接字,回收资源
}
close(comfd);
close(listfd);
return 0;
}
TCP客户端
tcp_client.c
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define SIZE 128
#define BACKLOG 5
#define PERROR(msg) do{perror(msg);exit(-1);}while(0)
int main(int argc,char *argv[])
{
if(argc < 3)
{
fprintf(stderr,"Usage:%s <ip> <port>\n",argv[0]); //键盘输入IP和端口
return -1;
}
int comfd = socket(AF_INET,SOCK_STREAM,0);
if(comfd < 0)
PERROR("socket");
struct sockaddr_in sev;
sev.sin_family = AF_INET;
sev.sin_port = htons(atoi(argv[2])); //服务器端口号
sev.sin_addr.s_addr = inet_addr(argv[1]); //服务器IP
int confd =connect(comfd,(struct sockaddr*)&sev,sizeof(sev)); //主动请求连接
if(confd < 0)
PERROR("connet");
char buf[SIZE] = {0};
while(1)
{
printf("input:");
fgets(buf,sizeof(buf),stdin); //给服务器发送消息
buf[strlen(buf)-1]='\0'; //处理fgets自带的换行符
if(strncmp(buf,"quit",4) == 0)
break;
write(comfd,buf,sizeof(buf)); //发送
memset(buf,0,sizeof(buf)); //清空
read(comfd,buf,sizeof(buf)); //接收服务器回复的消息
printf("recv from ser:%s\n",buf);
}
close(comfd); //关闭套接字
return 0;
}
编译结果
1@ubuntu:~/socket$ ./sev
listen----------[192.168.47.61]$##[56410]
buf:hello
buf:I am client
listen----------
1@ubuntu:~/socket$ ./cli 192.168.47.61 9090
input:hello
recv from ser:xka
input:I am client
recv from ser:xka
input:quit