socket套接字实现两台主机的无线通信
前言
一、TCP编程是什么?
TCP编程是基于TCP协议的客户端和服务器程序之间的数据传输。TCP编程通常使用socket编程接口实现,因此也称为socket编程。
二、TCP编程分几步?
首先TCP编程分为两个部分
TCP服务器
1>建立sock套接字
2>绑定IP和端口号
3>监听
4>等待客户端连接
5>接收/发送
TCP客户端
1>建立sock套接字
2>主动连接服务器
3>接收/发送
服务端部分
1.建立sock套接字---->创建一个具有网络属性的文件描述符
socket函数原型如下:
#include <sys/types.h> /* 头文件*/
#include <sys/socket.h>
int socket(int domain, int type, int protocol);//函数原型
函数功能: 建立sock套接字 (具有网络通信功能的文件标识符)
参数: domain:地址族
AF_UNIX, AF_LOCAL Local communication unix(7) 本地连接协议
AF_INET IPv4 Internet protocols ip(7) IPv4协议
AF_INET6 IPv6 Internet protocols ipv6(7) IPv6协议
参数:type:协议
SOCK_STREAM:流式套接字(TCP)
SOCK_DGRAM: 数据报文套接字(UDP)
SOCK_RAW: 原始套接字 不选用任何协议
参数:protocol:参数生效值
默认为:0 --->前两个参数生效
返回值:
成功返回 套接字文件描述符
失败返回-1,并设置错误码
2.绑定IP和端口号
bind函数原型如下:
#include <sys/types.h> /* 头文件 */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,\
socklen_t addrlen);
参数:
sockfd:套接字文件描述符
addr:struct sockaddr * 赋值的结构体指针
addrlen:addr的长度
返回值:
成功返回0
失败返回-1,并设置错误码
bind第二个参数一般会使用sockaddr_in 结构体
struct sockaddr_in {
short sin_family; // 地址族
__be16 sin_port; // 端口号
struct in_addr sin_addr; // IP地址
s_addr // 专门给IPv4地址赋值的
unsigned char sin_zero[8]; // 填充位
};
网络编程的难点:需要进行小端序和大端序的转换
__be16 sin_port; /* Port number */端口号
struct in_addr sin_addr; /* Internet address */IP地址
这时候就需要引入一个函数htons(uint16_t hostshort)
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); 针对32位
uint16_t htons(uint16_t hostshort); 针对16位
前两个:小转大
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
后两个:大转小
由于系统是不会自动帮你把点分十进制转化为网络二进制的 所以,我们需要自行获取IP地址的网络二进制
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
功能:
将点分十进制转化为网络二进制
参数:
cp:字符串IP地址返回值:
返回值:
成功返回二进制地址
失败转化无效,返回INADDR_NONE
3.监听
为了防止一次性太多客户端接入的情况发生,我们需要限制一下服务器 同一时刻接入客户端的最大值
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能: 监听 :限制服务器同一时刻被连接的最大值
参数:
SOCKFD: socket套接字
backlog:同一时刻被连接的最大值
返回值:
成功返回0
失败返回-1,并设置错误码
4.等待客户端连接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:socket套接字
addr:客户端的IP
addrlen:addr的长度: 需要指针
返回值:
成功返回客户端的fd
失败返回-1,并设置错误码
客户端部分
1.建立sock套接字
请看上面的服务端部分的介绍
2.声明服务器的IP和端口号(给地址结构体赋值)
跟服务端部分的操作无异,少了一步bind函数的调用
3.主动连接服务器
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能: 主动连接服务器
参数:
sockfd: socket套接字
addr:服务器的IP和端口号
addrlen:结构体的大小
返回值:
成功返回0
失败返回-1,并设置错误码
4.收/发
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd:数据来源
buf:数据存放的地方
len:buf的长度
flags:0 阻塞
返回值:
成功返回接收的字节数
失败返回-1,并设置错误码
如果断开连接返回0
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:
发送数据
参数:
sockfd:给谁发
buf:数据存放的地方
len:buf的长度
flags:0 阻塞
返回值:
成功返回发送的字节数
失败返回-1,并设置错误码
总结
TCP编程本质上就是操作socket套接字(具有网络通信功能的文件标识符)来实现主机一对一的连接通信。其所有函数都是为了逐渐完善通信的功能。而接下来是我编写的一个通过socket套接字实现的服务器和客户端的双向通信例程,其核心原理就是开辟2个独立的进程来进行数据的收发,互不干扰阻塞。
服务端
#include <stdio.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
/*
int socket(int domain, int type, int protocol);
*/
int main(void)
{
//1>建立socket连接
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
perror("socket");
return -1;
}
printf("sockfd = %d\n",sockfd);
//绑定IP和端口号
struct sockaddr_in server;//声明结构体,并即将给里面的成员赋值
server.sin_family = AF_INET;//表示使用IPv4地址协议
server.sin_port = htons(12345);//端口号 将12345转成大端序
server.sin_addr.s_addr = inet_addr("192.168.60.119");//ip赋值
if(bind(sockfd,(struct sockaddr *)&server,sizeof(server))){
perror("bind");
return -1;
}
//3>监听--->服务器的保护机制
listen(sockfd,8);//当前服务器,同一时刻连接客户端的最大值为8
//等待客户端连接
struct sockaddr_in client;//用来保存客户端的IP和端口号
int len = sizeof(client);
int fd;
fd = accept(sockfd,(struct sockaddr *)&client,&len);
if(fd < 0){
perror("accept");
return -1;
}
printf("客户%d上线了\n",fd);
pid_t pid = fork();
if(pid < 0){
perror("pid");
return -1;
}
char r_buf[50];
char w_buf[50];
if(pid > 0){
while(1){
printf("我是父进程\n");
bzero(w_buf,sizeof(w_buf));
scanf("%s",w_buf);
send(fd,w_buf,strlen(w_buf),0);
}
}
if(0 == pid){
while(1){
printf("我是子进程\n");
bzero(r_buf,sizeof(r_buf));
recv(fd,r_buf,sizeof(r_buf),0);
printf("%s\n",r_buf);
}
}
return 0;
}
客户端
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
/*建立sock套接字
* 声明服务器的IP端口号
* 主动连接服务器
* 收/发
* */
int main(int argc,char *argv[])
{
if(argc != 3){
printf("User:%s <IP><PORT>\n",argv[0]);
return -1;
}
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
perror("socket");
return -1;
}
printf("sockfd = %d\n",sockfd);
//声明IP和端口号
struct sockaddr_in server;//声明结构体,并即将给里面的成员赋值
server.sin_family = AF_INET;//表示使用IPv4地址协议
server.sin_addr.s_addr = inet_addr(argv[1]);//ip赋值
server.sin_port = htons(atoi(argv[2]));//端口号 将12345转成大端序
//3>主动连接服务器
if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))){
perror("connect");
return -1;
}
printf("连接成功\n");
//4>发
pid_t pid = fork();//开辟2个进程
char r_buf[50];
char w_buf[50];
if(pid > 0){
while(1){
printf("我是父进程\n");
bzero(w_buf,sizeof(w_buf));
scanf("%s",w_buf);
send(sockfd,w_buf,strlen(w_buf),0);
}
}
if(pid == 0){
while(1){
bzero(r_buf,sizeof(r_buf));
printf("我是子进程\n");
recv(sockfd,r_buf,sizeof(r_buf),0);
printf("%s\n",r_buf);
}
}
return 0;
}