简介
之前已经介绍过UDP简单介绍与通讯模拟。本篇博客是使用socket网络编程,会先介绍一些socket函数的用法,之后实现一个简单的聊天功能,最后拓展了socket中IPV4的地址结构和网络通用地址结构。
函数关系流程图
函数介绍
socket()
//头文件
#include <sys/socket.h>
//创建socket
int socket(int family, int type, int protocol);
//参数介绍
family : 协议族 (AF_INET , AF_INET6 , PF_PACKET...) 对应IPV4 , IPV6和链路层编程接口
type : 套接字类型(SOCK_STREAM , SOCK_DGRAM , SOCK_RAM...)流式套接字 \ 数据报式套接字 \ 原始套接字
protocol : 协议类型(0 , IPPROTO_TCP , IPPROTO_UDP)为0自动寻找 、
//返回值
得到socket
//NOTE
创建的socket默认是主动的,即默认是客户端主动请求,系统不会分配端口。当服务器主动创建时,会需要进行一定的修改,比如绑定端口。
sendto()
功能:向to指针指向的地址发送UDP数据
ssize sendto(
int sockfd,
const void * buf,
size_t nbytes,
int flags,
const struct sockaddr *to,
socklen_t addrlen);
参数介绍
sockfd :发送数据使用的套接字
buf :发送的数据缓冲区
nbytes :发送消息的长度
flags :一般为0,经典的套接字发送。
to :指向目的主机地址结构体的指针
addrlen :to指向的地址长度
NOTE
通过to和addrlen确定目的主机
可以发送长度为0的数据包
如果UDP套接字没有绑定ip和端口信息,那么第一次调用sendto的时候系统会分配本地主机ip和一个临时端口。
RETURN
发送数据的字节数,失败返回 -1
example
发送注意替换IP地址和端口号
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(void)
{
//1. create socket
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
//2. send data
//define a IPV4 address
struct sockaddr_in dst_addr;
//clear struct,因为socketaddt_in中需要补位0,所以我们可以直接初始赋值为0
//memset(&dst_addr,0,sizeof(dst_addr));
bzero(&dst_addr,sizeof(dst_addr));
//协议
dst_addr.sin_family = AF_INET;
// 将主机字节序转换为网络字节序
dst_addr.sin_port = htons(8080);
//将字符串IP转换为32bit整形数据
inet_pton(AF_INET,"0.0.0.0",&dst_addr.sin_addr.s_addr);
char data[40] = "Hello,My name is ffy.Nice to meet you!";
int num = sendto(sockfd,data,strlen(data),0,(struct sockaddr *)&dst_addr, sizeof(dst_addr));
printf("%d\n\n\n\n\n",num);
close(sockfd);
return 0;
}
bind()
让套接字绑定一个固定的IP和端口。
int bind(
int sockfd,
const struct sockaddr *myaddr,
socklen_t addrlen
);
参数介绍
sockfd :套接字
myaddr :指向特定协议的地址结构指针
addrlen :该地址结构的长度
返回值
成功:返回0
失败:其他
recvfrom()
ssize_t recvfrom(
int sockfd,
void *buf,
size_t nbytes,
int flags,
struct sockaddr * from,
socklen_t *addrlen
);
参数
sockfd :从该套接字接收数据
buf :接收数据缓冲区
nbytes :接收数据缓冲区大小
flags :套接字标志(常为0)
from :源地址结构体指针,用来保存数据的来源
addrlen :from所指内容的长度
NOTE
from、addrlen用来保存数据的来源信息,可以为空。
RETURN
成功接受的字符数,出错返回-1。
example
用上面写的发送函数发送数据,用以下代码接受数据。以下代码绑定了8080端口。
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void)
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(8080);
myaddr.sin_addr .s_addr = htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
while (1)
{
/* code */
char buf[128] = "";
int len = recvfrom(sockfd, buf, sizeof(buf), 0, NULL,NULL);
printf("the length is %d \t the buf is %s\n",len,buf);
}
close(sockfd);
return 0;
}
综合案例:聊天程序
NOTE
需要可以同时收发消息,需要开启多任务。
设计流程:
1、创建套接字
2、bind ip与端口信息
3、创建两个线程:接收(recvfrom)和发送(sendto)
代码
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
void *QQSend(void *arg)//arg保存的socket的地址
{
printf("send channel is created successfully and you can send messages now!\n");
int sockfd = *(int *)arg;
struct sockaddr_in dst_addr;
bzero(&dst_addr,sizeof(dst_addr));
dst_addr.sin_family = AF_INET;
socklen_t len_t = sizeof(dst_addr);
while (1)
{
char buf[128] = "";
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = 0; //去掉换行符
//解析地址
if (strncmp(buf,"sayto",5) == 0)
{
//sayto 192.168.31.96 8080
unsigned short port = 0;
char ip[16] = "";
sscanf(buf, "sayto %s %hd",ip,&port);
dst_addr.sin_port = htons(port);
inet_pton(AF_INET,ip,&dst_addr.sin_addr.s_addr);
continue;
}
else //要发送的消息
{
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&dst_addr,len_t);
}
}
return NULL;
}
void * QQRecv(void *arg)
{
printf("receive channel is created successfully also!\n");
int sockfd = *(int *)arg;
while (1)
{
char buf[128] = "";
struct sockaddr_in from;
socklen_t len = sizeof(from);
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &len);
unsigned short port = ntohs(from.sin_port);
char ip[16] = "";
inet_ntop(AF_INET, &from.sin_addr.s_addr , ip, 16);
printf("*******************************\n");
printf("from %s : %s\n",ip,buf);
printf("*******************************\n");
}
}
int main(void)
{
//create a new socket
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
unsigned short port = 8000;
if (sockfd < 0)
{
printf("Socket create ERROR!!!\n");
}
printf("please enter the port which you can use\n");
scanf("%hd",&port);
//bind ip and port
struct sockaddr_in client;
bzero(&client,sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(port);
client.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET,"192.168.31.96",&client.sin_addr.s_addr);
//socklen_t client_t = sizeof(client);
bind(sockfd,(struct sockaddr *) &client, sizeof(client) );
//创建一个发送线程
pthread_t tidS, tidR;
pthread_create(&tidS,NULL,QQSend,(void *) &sockfd);
pthread_create(&tidR,NULL,QQRecv,(void *) &sockfd);
pthread_join(tidS,NULL);
pthread_join(tidR,NULL);
return 0;
}
拓展:IPV4地址结构
//头文件:#include <netinet/in.h>
struct in_addr
{
in_addr_t s_addr; //四个字节
};
//IPv4地址结构
struct sockaddr_in
{
sa_family_t sin_family; //2字节,协议
in_port_t sin_port; //2字节,端口PORT
struct in_addr sin_addr; //4字节,IP地址
char sin_zero[8]; //8字节 ,为0,网络中套接字必须为16个字节,作为补位。
};
拓展:网络地址结构
//头文件: #include <netinet.in.h>
struct sockaddr
{
sa_family_t sa_family; // 2 byte
char sa_data[14];
}
以上两种地址结构的使用场景:
在定义源地址和目的地址的时候使用 struct sockaddr_in;
struct sockaddr_in my_addr;
在调用编程接口函数的时候,且该函数需要传入地址时,使用struct sockaddr
进行强制转换。
bind(sockfd , (struct sockaddr *)my_addr , sizeof(my_addr));
sendto(sockfd,data,strlen(data),0,(struct sockaddr *)&dst_addr, sizeof(dst_addr));
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &len);