目录
C/S模式:Client客户端、Server服务器
TCP编程基于socket套接字实现,因此也习惯称为Socket编程
一、创建套接字
cpp
#include <sys/types.h>
#include <sys/socket.h>
//创建套接字
int socket(int doman, int type, int protocol)
params: domain: AF_INET----IPV4
AF_INET6---IPV6
AF_UNIX,AF_LOCAL--本地通信
此外还有AF_NETLINK用于做设备驱动/内核用户间通信、AF_PACKET原始套接字
type: SOCK_STREAM 流式套接字 唯一对应TCP
SOCK_DGRAM 数据报套接字 唯一对应UDP
SOCK_RAW 原始套接字
protocol:一般填0
return:成功时返回fd,出错时返回-1
二、绑定套接字
cpp
#include <sys/types.h>
#include <sys/socket.h>
//绑定套接字----服务器使用
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
params: addr: //配置完sockaddr_in(见下)后---强转为sockaddr指针
addrlen: //
return:成功返回0,出错返回-1
//补充
struct sockaddr{ //通用结构体 仅作为一种模型,实际上要将下面的结构体配置完成后强转为此结构体
sa_family_t sa_family; //2B 协议族
char sa_data[14];//IP+端口号
}
struct sockaddr_in{ //基于Internet通信的结构体
sa_family_t sin_family;//2B AF_INET(IPV4)
in_port_t sin_port; //2B 端口号
struct in_addr sin_addr; //4B IP地址
unsigned char sin_zero[8]; //无实际意义 只是为了跟sockaddr大小对齐
}
struct in_addr{ //Internet地址---32位网络字节序
uint32_t s_addr;
}
IP地址转换补充:
cpp
#include <arpa/inet.h>
//将点分十进制形式IP地址的字符串 转换为32位网络字节序整数
//仅用于IPV4,出错时返回-1,因此不能转换全网广播255.255.255.255的地址
in_addr_t inet_addr(const char *cp)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//将点分十进制形式IP地址的字符串 转换为32位网络字节序整数
//IPV4和IPV6都可用,能正确处理全网广播的地址
int inet_pton(int af, const char *src, void *dst)
params: af----AF_INET或AF_INET6
src---源地址---点分十进制形式IP字符串
dst---转换结果地址---32位整数
return: 成功返回1 出错返回其他值
//和它反过来的,将32位整数转换为点分十进制形式IP地址字符串
//成功返回非空指针,失败返回NULL
const char *inet_ntop(int af, const void * src, char * dst, socklen_t size)
示例
cpp
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h> //exit
#include <string.h> //bzero
#include <strings.h> //strncasecmp 忽略大小写比较
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERV_PORT (5001)
#define SERV_IP "127.0.0.1"
#define BUFSIZE (128)
int main(void)
{
int sock_fd;
struct sockaddr_in sin;
//1.创建
if( (sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1 )
{
perror("socket");
exit(1);
}
//2.绑定 需要先配置sockaddr_in
bzero(&sin,sizeof(sin));//清零再填充
sin.sin_family = AF_INET;//IPV4
sin.sin_port = htons(SERV_PORT);//端口号转换网络字节序
//两种IP地址转换
//sin.sin_addr.s_addr = inet_addr(SERV_IP);
if( inet_pton(AF_INET,SERV_IP,(void*)&sin.sin_addr) != 1 )
{
perror("inet_pton");
exit(1);
}
if(bind(sock_fd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("bind");
exit(1);
}
三、监听套接字
cpp
#include <sys/types.h>
#include <sys/socket.h>
//监听套接字,server端将主动套接字--->被动套接字
int listen(int sockfd, int backlog)
params: backlog 一般填5 指同时允许几路客户端和服务器进行正在连接(正在三次握手)ARM最大为8
return:成功返回0,出错返回-1
内核中服务器的套接字fd会维护2个链表:
正在三次握手的客户端链表(数量=2*backlog+1)
已经建立好连接的客户端链表(已完成三次握手且分配好了newfd)
四、等待套接字
cpp
#include <sys/types.h>
#include <sys/socket.h>
//阻塞等待客户端连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
params: addr 客户端信息的指针(用户传入,连接完成内核保存进去),不需要的话填NULL
addrlen 客户端信息长度的指针,不需要的话填NULL
return: 成功返回已建立连接的新的客户端描述符newfd(非负整数)
出错返回-1
五、服务器端示例
cpp
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h> //exit
#include <string.h> //bzero
#include <strings.h> //strncasecmp 忽略大小写比较
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERV_PORT (5001)
#define SERV_IP "127.0.0.1"
#define BUFSIZE (128)
#define QUIT_STR "quit"
int main(void)
{
int sock_fd,newfd;
struct sockaddr_in sin;
char rd_buf[BUFSIZE];
int read_num;
//1.创建
if( (sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1 )
{
perror("socket");
exit(1);
}
//2.绑定 需要先配置sockaddr_in
bzero(&sin,sizeof(sin));//清零再填充
sin.sin_family = AF_INET;//IPV4
sin.sin_port = htons(SERV_PORT);//端口号转换网络字节序
//两种IP地址转换
//sin.sin_addr.s_addr = inet_addr(SERV_IP);
if( inet_pton(AF_INET,SERV_IP,(void*)&sin.sin_addr) != 1 )
{
perror("inet_pton");
exit(1);
}
if(bind(sock_fd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("bind");
exit(1);
}
//3.监听
if( listen(sock_fd,5) == -1 )
{
perror("listen");
exit(1);
}
//4.阻塞等待客户端连接 连接成功后需要newfd
if( (newfd = accept(sock_fd,NULL,NULL)) == -1 )
{
perror("accept");
exit(1);
}
while(1)
{
bzero(rd_buf,BUFSIZE); //读之前先请零
do
{
read_num = read(newfd,rd_buf,BUFSIZE-1);
}while(read_num < 0 && errno == EINTR);
if(read_num < 0)
{
perror("read");
}
if(read_num == 0) //客户端关闭
{
printf("Client is closed!\n");
break;
}
printf("Server received:%s\n",rd_buf);
if( strncasecmp(rd_buf,QUIT_STR,strlen(rd_buf)) == 0)//客户端输入quit
{
printf("Client is exiting!\n");
break;
}
}
close(newfd);.
close(sock_fd);
return 0;
}
六、连接套接字
cpp
#include <sys/types.h>
#include <sys/socket.h>
//连接服务器创建的套接字----客户端用
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
params: //参数同bind----表示配置方式相近
return: 成功返回0,出错返回-1
七、客户端示例
cpp
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h> //exit
#include <string.h> //bzero
#include <strings.h> //strncasecmp 忽略大小写比较
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERV_PORT (5001)
#define SERV_IP "127.0.0.1" //只能用ifconfig查到的
#define BUFSIZE (128)
#define QUIT_STR "quit"
int main(void)
{
int sock_fd;
struct sockaddr_in cin;
char wr_buf[BUFSIZE];
//1.创建
sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1)
{
perror("socket");
exit(1);
}
//2.连接服务器,还是一样先配置sockaddr_in
bzero(&cin,sizeof(cin));
cin.sin_family = AF_INET;
cin.sin_port = htons(SERV_PORT);
//cin.cin_addr.s_addr = inet_addr(SERV_IP);
if( inet_pton(AF_INET,SERV_IP,(void*)&cin.sin_addr) != 1 )
{
perror("inet_pton");
exit(1);
}
if( connect(sock_fd,(struct sockaddr*)&cin,sizeof(cin)) == -1 )
{
perror("connect");
exit(1);
}
//3.写数据
while(1)
{
bzero(wr_buf,BUFSIZE);//写之前先清零
if( fgets(wr_buf,BUFSIZE-1,stdin) == NULL)
{
continue;//重新获取
}
write(sock_fd,wr_buf,strlen(wr_buf));//写数据--相当于发送给服务器
if( strncasecmp(wr_buf,QUIT_STR,strlen(QUIT_STR)) == 0) //输入了quit
{
break;
}
}
close(sock_fd);
return 0;
}
八、Send和Recv
1.网络发送数据
cpp
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
params: 前三个参数同write
flags 一般填0---此时同write
MSG_DONTWAIT 非阻塞版本
MSG_OOB 用于发送TCP类型的带外数据(out-of-band)
2.网络接收数据
cpp
#include <sys/types.h>
#include <sys/socket.h>
ssize_t read(int sockfd, const void *buf, size_t len, int flags)
params: 前三个参数同read
flags 一般填0---此时同read
MSG_DONTWAIT 非阻塞版本
MSG_OOB 用于发送TCP类型的带外数据(out-of-band)
MSG_PEEK 偷看数据,读完不清除,非常实用
return: 成功返回收到字节数,失败返回-1