创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:domain 通信域,协议族
PF_LOCAL/PF_UNIX 本地套接字,进程间通信
PF_INET 基于IPv4的网络通信
PF_INET6 基于IPv6的网络通信
PF_PACKET 基于底层包的网络通信
参数:type 套接字类型
SOCK_STREAM 流式套接字,基于TCP协议
SOCK_DGRAM 数据报套接字,基于UDP协议
SOCK_RAW 原始套接字,工作在传输层以下
protocol:特殊协议,对于流式和数据报套接字而言,只能取0
返回值:成功返回表示套接字对象的文件描述符,失败返回-1
基本地址结构,本身没有实际意义,仅用于泛型化参数
struct sockaddr {
sa_family_t sa_family; 地址族
char sa_data[14]; 地址值
};
本地地址结构,用于AF_LOCAL/AF_UNIX域的本地通信
struct sockaddr_un {
sa_family_t sun_family; 地址族(AF_LOCAL/AF_UNIX)
char sun_path[]; 本地套接字文件的路径
};
网络地址结构,用于AF_INET域的IPv4网络通信
struct sockaddr_in {
sa_family_t sin_family; 地址族(AF_INET)
in_port_t sin_port; 端口号(0~65535) unsigned short
struct in_addr sin_addr; IP地址 unsigned int
};
网络地址结构,用于AF_INET域的IPv4网络通信
struct in_addr {
in_addr_t s_addr;
};
typedef uint16_t in_port_t; 无符号16位整数
typedef uint32_t in_addr_t; 无符号32位整数
include <sys/socket.h>
int bind(int sockfd, struct sockaddr const* addr, socklen_t addrlen);
功能:将套接字和本机的地址结构绑定在一起
参数:sockfd 套接字描述符
addr 自己的地址结构
addrlen 地址结构的字节数
返回值:成功返回0,失败返回-1
include <sys/socket.h>
int connect(int sockfd, struct sockaddr const* addr, socklen_t addrlen);
功能:将套接字和对方的地址结构连接在一起
参数:sockfd:套接字描述符
addr 对方的地址结构
addrlen 地址结构的字节数
返回值:成功返回0,失败返回-1
include <sys/socket.h>
int listen(int sockfd, int backlog)
功能:启动侦听,在指定套接字上启动对连接请求的侦听功能
参数:sockfd 套接字描述符,在调用此函数之前是一个主动套接字,是不能感知连接请求的,在调用此函数并成功返回之后,是一个被动套接字,具有感知连接请求的能力
backlog 未决连接请求队列的最大长度,一般取不小于1024的值
返回值:成功返回0,失败返回-1
include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
功能:等待并接受连接请求,在指定套接字上阻塞,直到连接建立完成
参数:sockfd 侦听套接字描述符
addr:输出连接请求发起方的地址信息
addrlen:输出连接请求发起方的地址信息字节数
返回值:成功返回可用于后续通信的连接套接字描述符,失败返回-1
include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t count, int flags);
功能:接收数据
参数:若flags取0则与read函数完全等价
MSG_DONTWAIT 以非阻塞方式接收数据
MSG_OOB 接收带外数据
MSG_WAITALL 等待所有数据,即不接收到count字节就不返回
返回值:成功返回实际接收到的字节数,失败返回-1
include <sys/socket.h>
ssize_t send(int sockfd, void const* buf, size_t count, int flags);
功能:发送数据
参数:若flags取0则与write函数完全等价
MSG_DONTWAIT 以非阻塞方式接收数据
MSG_OOB 接收带外数据
MSG_DONTROUTE 不查路由表,直接在本地网络中寻找目的主机
返回值:成功返回实际发送的字节数,失败返回-1字节序转换
uint32_t htonl(uint32_t hostlong);//长整形主机字节序到网络字节序
uint32_t ntohl(uint32_t netllong);//长整形网络字节序到主机字节序
uint16_t htons(uint16_t hostshort);//短整形主机字节序到网络字节序
uint16_t ntohs(uint16_t netshort);//短整形网络字节序到主机字节序
in_addr_t inet_addr(char const* ip);
点分十进制字符串地址 --》网络字节序形式整数地址
int inet_aton(char const* ip, struct in_addr* nip);
点分十进制字符串地址 --》网络字节序形式整数地址
char* inet_ntoa(struct in_addr nip);
网络字节序形式整数地址 --》点分十进制字符串地址| 步骤 | 服务器描述 | 服务器函数 | 客户机函数 | 客户机描述 | 步骤 |
|---|---|---|---|---|---|
| 1 | 创建套接字 | socket | socket | 创建套接字 | 1 |
| 2 | 准备地址结构 | sockaddr_in | sockaddr_in | 准备地址结构 | 2 |
| 3 | 绑定地址 | bind | — | — | — |
| 4 | 启动侦听 | listen | — | — | — |
| 5 | 等待连接 | accept | connect | 请求连接 | 3 |
| 6 | 接收请求 | recv | send | 发送请求 | 4 |
| 7 | 发送响应 | send | recv | 接收响应 | 5 |
| 8 | 关闭套接字 | close | close | 关闭套接字 | 6 |
//基于tcp协议的服务器
#include<stdio.h>
#include<string.h>
#include<ctype.h>// toupper()
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<errno.h>
//信号处理函数,负责收尸
void sigchild(int signum){
while (1){
pid_t pid = waitpid(-1, NULL, WNOHANG);
if(pid == -1){
if(errno == ECHILD){
printf("没有子进程");
break;
}else{
perror("waitpid");
return;
}
}else if (pid == 0){
printf("子进程运行中");
break;
}else{
printf("回收了%d\n",pid);
}
}
};
int main(void){
//17号信号捕获处理
if(signal (SIGCHLD,sigchild)== SIG_ERR){
perror("signal");
return -1;
};
printf("服务器:创建套接字\n");
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
perror("socket");
return -1;
}
printf("服务器:组织地址结构\n");
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(8981);//字节序
ser.sin_addr.s_addr = inet_addr("192.168.102.251");
printf("服务器:绑定套接字和地址结构\n");
if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
perror("bind");
return -1;
}
printf("服务器:启动侦听\n");
if(listen(sockfd,1024) == -1){
perror("listen");
return -1;
}
for(;;){
printf("服务器:等待客户端的连接\n");
struct sockaddr_in cli;//用来输出客户端的地址结构
socklen_t len = sizeof(cli);//用来输出地址结构大小
int conn = accept(sockfd,(struct sockaddr*)&cli,&len);
if(conn == -1){
perror("accept");
return -1;
}
printf("服务器:接收到%s:%hu的客户端的连接\n",
inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
printf("服务器:业务处理\n");
//创建子进程,子进程负责和客户端通信
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
//关闭侦听套接字
close (sockfd);
for(;;){
//接收客户端发来的小写的串
char buf[64] = {};
ssize_t size = read(conn,buf,sizeof(buf)-1);
if(size == -1){
perror("read");
return -1;
}
if(size == 0){
//对方关闭套接字
break;
}
//转大写
for(int i = 0;i < strlen(buf);i++){
buf[i] = toupper(buf[i]);
}
//回传给客户端
if(write(conn,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
}
//通信结束,关闭通信套接字
close (conn);
return 0;
}
//父进程代码
close(conn);
}
printf("服务器:关闭套接字\n");
close(sockfd);
return 0;
}//基于tcp的客户端
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
int main(void){
printf("客户端:创建套接字\n");
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
perror("socket");
return -1;
}
printf("客户端:组织服务器的地址结构\n");
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(8980);
ser.sin_addr.s_addr = inet_addr("192.168.249.129");
printf("客户端:发起连接请求\n");
if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
perror("connect");
return -1;
}
printf("客户端:业务处理\n");
for(;;){
//通过键盘获取小写的串
char buf[64] = {};
fgets(buf,sizeof(buf),stdin);
//! 退出
if(strcmp(buf,"!\n") == 0){
break;
}
//发给服务器
if(send(sockfd,buf,strlen(buf),0) == -1){
perror("send");
return -1;
}
//接收回传的大写的串
if(recv(sockfd,buf,sizeof(buf)-1,0) == -1){
perror("recv");
return -1;
}
//显示
printf("%s",buf);
}
printf("客户端:关闭套接字\n");
close(sockfd);
return 0;
}