目录
1 socket网络编程步骤
1.1 服务器编程步骤
1.2 客户端编程步骤
1.3 函数以及结构解释
2 基于TCP的一对多网络编程
2.1 服务器编程步骤
2.2 客户端编程
2.3 函数解释
2.4 基于TCP的网络聊天室
3 基于UDP的一对多编程
3.1 服务器编程步骤
3.2 客户端编程步骤
3.3 UDP下的读写数据
3.4 函数解释
3.5 基于UDP的时间服务器代码
本文要讲解Unix下的网络编程,即socket编程,也称为套接字编程。
Unix系统在网络上功能十分强大,历史悠久,因此有一个非常固定的套路,代码比较僵化,没有改变的余地。Socket编程主要包括两个方面,一个是本地通信,即计算机内部的进程间的通信(IPC),这个本文不过多讲解,主要集中在后面的内容,网络通信。
本文先介绍Unix网络编程的基本框架,以及具体的函数用法、结构体,然后分别介绍TCP网络编程和UDP网络编程。本文只介绍编程框架,而不会涉及具体的协议。
网络编程要考虑两个方面:服务器端和客户端,客户端通过网络与服务器端通信。
1、调用函数socket(),用来创建socket描述符。
int fd=socket(AF_INET,SOCK_DGRAM,0);
if(fd==-1)
perror("socket"),exit(-1);
2、准备通讯地址(三个结构体),进行数据交互。
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(2222);//服务器的端口
addr.sin_addr.s_addr=inet_addr("192.168.1.112");//服务器的ip地址
3、绑定通讯地址和socket描述符,函数bind()
int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
if(res==-1)
perror("bind "),exit(-1);
else
printf("绑定成功\n");
4、读写描述符,和读写文件描述符一样,函数read()/write()。
char buf[100]={};
res=read(fd,buf,sizeof(buf));
5、使用函数close()关闭socket描述符。
close(fd);
1、调用函数socket(),用来创建socket描述符。
int fd=socket(AF_INET,SOCK_DGRAM,0);
if(fd==-1)
perror("socket "),exit(-1);
2、准备通讯地址(三个结构体),进行数据交互。
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(2222);//服务器
addr.sin_addr.s_addr=inet_addr("192.168.1.112");
3、绑定通讯地址和socket描述符,函数connect()
int res=connect(fd,(struct sockaddr*)&addr,sizeof(addr));
if(res==-1)
perror("connect "),exit(-1);
4、读写描述符,和读写文件描述符一样,函数read()/write()。
write(fd,"hello",5);
5、使用函数close()关闭socket描述符。
close(fd);
除了1.3的bind()换成connect()之外,其他的与服务器端一样,而且connect()和bind()用法完全一样。
1、socket() 函数
int socket( int domin , int type , int protocol);
参数解释
domin用于选择协议簇,可以选择一下的宏:
AF_UNIX/AF_LOCAL/AF_FILE :本地通信
AF_INET : 网络通信IPv4 (一般使用这个)
AF_INET6 :网络通信IPv6
type 用于选择通信的类型,主要包括:
SOCK_STREAM :数据流,用于TCP。
SOCK_DGRAM :数据报,用于UDP。
protocol参数本来应该指定协议,但是由于协议已经被前两个参数确定了,所以直接将protocol给0即可,这是Unix网络编程的僵化的体现。
如果函数执行成功,返回socket描述符,失败返回-1.
这个函数用来生成一个socket描述符。
2、结构体
网络通信需要三个结构体来指定网络信息,分别是struct sockaddr、struct sockaddr_un、struct sockaddr_in。其中sockaddr主要用于做函数的参数,并不储存数据,即没啥实际的用处,socjaddr_un负责存储本地通信的地址数据,sockaddr_in负责存储网络通信的地址数据。
#include
struct sockaddr_un{
int sun_family; //用于指定协议簇,和socket()要保持一致
char sun_path[]; //存socket文件名,作为交互的媒介
}
#include
struct sockaddr_in{
int sin_family; //用于指定协议簇,和socket()一致
short sin_port; //端口号
struct in_addr sin_addr; //存储IP地址的结构
}
注:sockaddr_in / sockaddr_un 在做参数时必须类型转换为sockaddr,读写数据时,一方读数据,另一方必须写数据。
3、bind()
int bind(int sockfd , struct sockaddr* addr , socklen_t size);
参数解释
sockfd是socket文件描述符,即socket()函数的返回值。
addr是通信地址的指针,需要做类型转换
size是通信地址的大小,即sizeof(struct)。
这个函数用来绑定自己的IP地址和端口号
4、connect()
int connect(int sockfd , const struct sockaddr *addr , socklen_t addrlen);
参数解释
sockfd是socket文件描述符,即socket()函数的返回值。
addr是通信地址的指针,需要做类型转换
size是通信地址的大小,即sizeof(struct)。
TCP客户通过connect函数与服务端进行通信。
所谓的一对多编程,即只有一个服务器,有很多个客户端,现在基本都是一对多编程。TCP是一个基于连接的协议,在网络交互中,服务器和客户端要保持连接,不能断开。如果出现数据错误,TCP会重新发送,保证数据的正确和完整,但是资源的消耗比较大。
1、socket()得到一个socket描述符
2、准备通信地址 struct sockaddr_in
3、绑定bind(),连接端口
4、监听函数listen(),这个函数用来控制同一时刻连接的人数。
5、等待客户端的连接,函数accept(),返回一个新的socket描述符,这个新的socket描述符用于读写交互。
6、读写函数read()/write()
7、关闭socket()
1、调用函数socket(),用来创建socket描述符。
2、准备通讯地址(三个结构体),进行数据交互。
3、绑定通讯地址和socket描述符,函数connect()
4、读写描述符,和读写文件描述符一样,函数read()/write()。
5、使用函数close()关闭socket描述符。
这里的步骤和上面的客户端编程的步骤是一样的
1、listen()
该函数主要用于设置当有多个用户请求时,放入等待队列,等待队列的最大长度就是listen设置的。
int listen( int sockfd , int backlog);
参数解释
sockfd 是你要设置的socket描述符
backlog是队列的最大长度
2、accept()
int accept(int fd , struct sockaddr *addr , socklen_t *len);
参数解释
fd就是socket描述符
addr是一个结构体指针,用于传出客户端的通信地址
len是一个传入传出参数,传入addr的真实长度,传出接收到的客户端的通信地址的真实长度。(一般是相同的)
返回:成功返回新的socket描述符,失败返回-1.
1、服务端代码
#include
#include
#include
#include
#include
#include
#include
#include
int sockall[100]; //存放socket描述符的数组
int sum; //聊天室人数
int socket_findsit(){
for(int i=0;i<100;i++)
if(!sockall[i])
return i;
return -1;
}
void* task_chat(void* p){
int index=(int)p;
int fd=sockall[index];
char buf[100]={};
char name[100]={};
char news[200]={};
int i=0;
sum++;
printf("聊天室人数:%d\n",sum);
/*读取用户的用户名*/
int res=read(fd,name,sizeof(name));
printf("用户%s进入聊天室\n",name);
/*进入聊天环节*/
while(1){
res=read(fd,buf,sizeof(buf));
if(res==0)
break;
if(!strcmp(buf,"bye")){
printf("用户%s退出聊天室\n",name);
break;
}
sprintf(news,"%.*s:%.*s",strlen(name),name,strlen(buf),buf);
for(i=0;i<100;i++){
if(sockall[i])
write(sockall[i],news,strlen(news));
}
memset(buf,0,strlen(buf));
memset(news,0,strlen(news));
}
close(fd);
sockall[index]=0;
sum--;
printf("聊天室人数:%d\n",sum);
return NULL;
}
int main(){
pthread_t id;
int fd=socket(AF_INET,SOCK_STREAM,0);//TCP协议
if(fd==-1)
perror("socket "),exit(-1);
struct sockaddr_in addr;
addr.sin_family=AF_INET; //协议簇
addr.sin_port=htons(2222);
addr.sin_addr.s_addr=inet_addr("192.168.1.112");
/*防止复用*/
int reuse=1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
if(res==-1)
perror("bind "),exit(-1);
printf("成功绑定端口\n");
listen(fd,100);//设置等待队列的长度
int index;
while(1){
struct sockaddr_in from;
socklen_t len=sizeof(from);
int sockfd=accept(fd,(struct sockaddr*)&from,&len);
index=socket_findsit();
if(index!=-1){
sockall[index]=sockfd;
pthread_create(&id,0,task_chat,(void*)(index));
}
else{
printf("聊天室人数已满\n");
}
}
}
2、客户端代码
#include
#include
#include
#include
#include
#include
#include
#include
void* task_read(void*p){
int fd=(int)p;
char buf[200]={};
while(1){
read(fd,buf,sizeof(buf));
printf("%s\n",buf);
memset(buf,0,strlen(buf));
}
}
int main(){
pthread_t id;
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd==-1)
perror("socket "),exit(-1);
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(2222);
addr.sin_addr.s_addr=inet_addr("114.116.5.220");
int res=connect(fd,(struct sockaddr*)&addr,sizeof(addr));
if(res==-1)
perror("connect "),exit(-1);
printf("成功连接服务器!\n");
char buf1[100]={};
char buf2[100]={};
printf("请输入您的用户名:\n");
scanf("%s",buf1);
write(fd,buf1,strlen(buf1));
memset(buf1,0,sizeof(buf1));
printf("欢迎来到聊天室!\n");
pthread_create(&id,0,task_read,(void*)fd);
while(1){
scanf("%s",buf1);
write(fd,buf1,strlen(buf1));
if(!strcmp(buf1,"bye"))
break;
memset(buf1,0,strlen(buf1));
}
close(fd);
}
UDP协议,用户数据报协议,无需连接,消耗资源少,但是有可能出错。
1、socket(),得到socket描述符
2、准备通信地址,strutc sockaddr_in
3、绑定bind()
4、读写
5、关闭描述符
1、socket(),得到socket描述符
2、准备通信地址,strutc sockaddr_in
3、读写
4、关闭描述符
客户端不需要绑定或者连接。
UDP协议下,使用的读写函数与TCP不同,在不连接的前提下,读数据使用read()或者recvfrom(),read()只能读数据,不能读对方发送方的通信地址,而recvfrom两者皆可。写数据使用sendto(),不能使用write()。
1、recvfrom()
size_t recvfrom( int fd , void *buf , size_t len , int flags , struct sockaddr *src_addr , socklen_t *addrlen);
参数解释
fd 文件描述符,在这里就是socket描述符,buf是要将文件读取进的对象,len是读取的长度,flags一般给0即可,src_addr是一个传出参数,用于接收发送方的通信地址,addrlen是一个传入传出参数,传入addr的真实长度,传出接收到的客户端的通信地址的真实长度。
2、sendto()
int sendto(int fd , void *addr , size_t len , int flag , struct sockaddr *addr , socklen_t addlen);
参数解释
fd是文件描述符,addr是要写入的内容指针,len是要写入的长度,addr 传入数据接收方的通信地址,addrlen就是通信地址的长度。
返回:成功返回发送的字节数,失败返回-1。
1、服务器代码
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
int fd=socket(AF_INET,SOCK_DGRAM,0);
if(fd==-1)
perror("socket "),exit(-1);
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(2222);
addr.sin_addr.s_addr=inet_addr("192.168.1.112");
int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
if(res==-1)
perror("bind "),exit(-1);
printf("bind ok\n");
char buf[100]={};
while(1){
struct sockaddr_in from;
socklen_t len=sizeof(from);
res=recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,&len);
pid_t pid=fork();
if(!pid){
time_t time1=time(NULL);
struct tm* tm1=localtime(&time1);
printf("读到了%d字节,内容:%s\n",res,buf);
memset(buf,0,sizeof(buf));
sprintf(buf,"%d/%d/%d %d:%d:%d",tm1->tm_year+1900,tm1->tm_mon+1,tm1->tm_mday,tm1->tm_hour,tm1->tm_min,tm1->tm_sec);
sendto(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,len);
exit(0);
}
}
}
2、客户端代码
#include
#include
#include
#include
#include
#include
int main(){
int fd=socket(AF_INET,SOCK_DGRAM,0);
if(fd==-1)
perror("socket "),exit(-1);
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(2222);
addr.sin_addr.s_addr=inet_addr("192.168.1.112");
sendto(fd,"hello",5,0,(struct sockaddr*)&addr,sizeof(addr));
char buf[100]={};
int res=read(fd,buf,sizeof(buf));
printf("日期:%s\n",buf);
close(fd);
return 0;
}