C/C++程序员进阶课堂|纯新网络编程教学

纯新系列文章 第三章:网络编程(socket)
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 纯新系列文章
  • 前言
  • 一、IP地址,端口和协议
  • 二、相关知识点
    • 1.数据结构:sockaddr_in
    • 2.字节顺序
    • 3.模型
  • 三.Socket编程
    • socket()
    • connect()
    • 读写操作
    • close()
    • 客户端实战TCP
    • bind()
    • listen()和accept()
    • select()
    • 服务端TCP
    • 其他函数
    • udp客户端
    • udp服务端
  • 总结

前言 hello!大家好,本节内容是纯新系列内容的第三节,网络编程,前两节分别为进程和线程,感兴趣的朋友可以通过点击结尾的链接来学习。
提示:以下是本篇文章正文内容。
一、IP地址,端口和协议 IP地址:Internet中唯一的地址标识,目前广泛使用的ip地址为32位(4个字节),每一个Internet都必须带有IP地址。
表示:每个字节的数字用十进制表示并用点隔开。 (111.111.23.111)
端口(TCP\Udp):目的主机接收到数据包后可通过端口传递给运行进程中的某一个。
大小:0~65536
协议 :一种通信规则,有TCP[传输控制协议] 和UDP[用户数据报协议]两种协议

区别:TCP 协议可以看做是打电话,必须在对方的同意下才能传输数据,UDP协议就好比发短信,用户只管发,并不知道对方有没有收到,这种情况可能会造成丢数据的情况,所以UDP经常会出现在视音频的传输中。

二、相关知识点 1.数据结构:sockaddr_in 在网络编程中,操作系统本身以结构体struct sockaddr来存储相关地址信息,但程序员无法 直接使用结构体struct sockaddr,而是通过操作struct sockaddr_in来间接向struct sockaddr写入数据,在字节上,两者是等大的,下面是struct sockaddr的的结构体组成
struct in_addr{ unsigned long s_addr; }; struct sockaddr_in{ short int sin_family; //族(AF_INET和PF_INET0 unsigned short in sin_port; //端口 struct in_addr; //IP地址 unsigned char sin_zero[8]; }; //实例 初始化sockaddr_in intmain() { struct sockaddr_in my_addr; memset(&my_addr,0,sizeof(my_addr)); //初始化IP地址memset(a,0,sizeof(a)//将a的内容为0 my_addr.sin_family=AF_INEF; //一般为AF_INEF,表示使用IP。 my_addr.sin_port= htons(3490); my_addr.sin_addr.s_addr=inet_addr("135.241.5.10"); //inte_addr把字符串改变为IP地址类型 }

补充:上面的程序涉及到了一些变量的转换函数,程序如下
//下面是两个转换函数 #include in_addr_t inet_addr(const char*strptr); //字符串转地址 char*inet_ntoa(struct in_addr inaddr); //地址转字符串//下面是字节序相关的转换函数, #include uint32_t htons(uint16_t hostshort); //将主机字节序改为网络字节序

2.字节顺序 主机字节顺序:指的是整数在内存中保存的顺序,不同的CPU有不同的顺序,有小端字节序和大端字节序之分。
网络字节顺序: TCP/IP协议规定,网络数据流应采用大端字节序,既低地址高字节

//字节序的转换 #include uint32_t htonl(uint 32_t hostlong); //32位主机地址转为网络地址格式 uint32_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); //网络地址转为主机地址格式 uint32_t ntohl(uint16_t netshort);

3.模型 在编程之前,我们首先要知道如下的模型
【C/C++程序员进阶课堂|纯新网络编程教学】C/C++程序员进阶课堂|纯新网络编程教学
文章图片

三.Socket编程 socket() socket:它一个描述符。类似于文件中的文件描述符
int socket(int flmily,int type,int protocol); //参数:地址族类型协议 //地址族:AF_INET ,AF_INET6,AF_LO //类型:SOCK_STEREAM,SOCK_DGRAM,SOCK_RAW //协议:IPPROTO_TCP,IPPTOTO_UDP,IPPROTO_SCTP //成功返回非负描述符,错误返回-1

connect() 该函数是为了在客户端建立于服务器的连接
int connect(int sockfg,struct sockaddr *servaddr , int addrlen); //socket描述符 服务器的socket地址 socket地址的长度成功返回0,失败返回-1。

读写操作
#include ssize_t read(int fa,void *buf,size_t count); //读 //fa:描述符 buf需要接收的数据 count 接收数据的大小sizeof(bufO ssize_t write(int fd,const void *buf,size_t count); //写strlen(buf)#include #include int recv(int sockfd,void *buf ,int len,int flags); //接收数据 len 为缓冲区大小,flags为接收标志 MSG_DONTWAIT 0 //成功返回实际接收的字节数,否则返回-1//缺省是阻塞函数,直到接收到信息或出错,关于阻塞,前面的进程中有提到概念 int send(int sockfd,const void *msg,int len,int flags); //数据发送函数,成功返回已经发送的字节数,否则返回=1、也是阻塞函数。//注意,如果函数返回值于参数len不相等,则剩余的未发送信息将再次发送 ssize_t sendto(int sock,const void * buf,size_t len,inf flags ,const struct sockaddr * to, socklen_t tolen); //to:数据接受者协议族地址成功返回发送字节数,出错返回-1 ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct sockaddr * from,socklen_t *addrlen); //from :数据发送者协议地址成功返回接收字节数,出错返回-1

close() 用来关闭特定的socket连接
int close(int sockfd); //关闭用于listen()函数的socket将禁止其他Cilent的连接请求

客户端实战TCP
#include #include #include #include #include #include #include #include #include int main() { char buf[1024]={0}; //数据接收 struct sockaddr_in sevAddr; memset(&sevAddr,0,sizeof(sevAddr)); sevAddr.sin_family = AF_INET; sevAddr.sin_port = htons(8899); sevAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int sockfd = socket(AF_INET,SOCK_STREAM,0); //创建一个socket描述符 if(sockfd == -1) { perror("socket"); //失败打印失败原因 return 0; } if(connect(sockfd,(struct sockaddr *)&sevAddr ,sizeof(sevAddr))==-1)//连接服务器 { perror("connect"); return -1; } printf("oj"); pid_t pid=fork(); //申请一个进程 while(1) { if(pid == 0)//子进程用来读信息 { if(read(sockfd,buf,sizeof(buf))==0) { exit(0); //如果读端{服务器}关闭,进程直接退出。 } printf("buf=%s\n",buf); memset(buf,0,sizeof(buf)); } if(pid>0)//父进程用来发送信息 { write(sockfd,"okok",strlen("okok")); } } close(sockfd); return 0; }

提示:读写函数都具有阻塞的特性,为了不影响收发(随时收和发,互不影响)上面的函数我用了两个进程分别来写收发,当然也有不用进程的方法,比如说select()可以判断出读端和写段的事件,我们放在服务器中讲述。
bind() 服务器用来绑定端口的函数,出错返回-1
int bind(int sockfd,struct sockaddr * my_addr,int addrlen);

listen()和accept() listen的作用相当于一个接收队列,当一个新的请求被放在接收队列中,服务器调用accept()函数来接收请求
int listen(int sockfd ,int queue_length); //queue_length为接收队列的长度,是accept函数之前最大允许连接的请求数,多余则拒绝 int accept(int sockfd,struct sockaddr *addr ,int *addrlen); //产生一个新的socket描述符,当有三个客户端连接,会一次产生三个描述符,语句需要被执行三次,为阻塞函数,addr为客户端的地址信息

select() select()函数的作用是允许进程提示内核等待多某一或多个事件发生或经历一段时间后唤醒他。正确返回就绪描述符的个数,超时返回0,出错返回-1。
int select(int maxfd,fd_set *readset,fd_*writeset,fd_set *exceptset,const struct timeval*timeout)//maxfd = 最大描述符+1中间分别为读事件,写事件和异常事件,timeout为NULL表示一直等待直到有描述符准备好,为0表示不等待,轮询问。//事件相关函数 fd _set read; //定义一个读事件 void ZD_ZERO(fd_set *fdest); //初始化 void FD_SET(int fd,fd_set *fdest); //添加fd void FD_CLR(int fd,fd_set *fdset); //删除fd void FD_ISSET(int fd,fd_set *fdset); //判定事件

服务端TCP
include .h> #include #include #include #include #include #include int main() { struct sockaddr_in myaddr; //服务端地址信息 struct sockaddr_in clientaddr; //客户端地址信息 int clilen = sizeof(clientaddr); memset(&clientaddr,0,sizeof(clientaddr)); memset(&myaddr,0,sizeof(myaddr)); myaddr.sin_family = AF_INET; myaddr.sin_port = htons(8888); myaddr.sin_addr.s_addr = inet_addr("10.32.556.192"); int listfd = socket(AF_INET,SOCK_STREAM,0); //设置socket描述符 if(listfd == -1) { perror("socket"); return -1; } if(bind(listfd,(struct sockaddr *)&myaddr,sizeof(myaddr)) == -1)//绑定地址 { perror("bind"); return -1; } if(listen(listfd,5) == -1)//连接队列最大设置为5 { perror("listen"); return -1; } int confd=0; char buf[120]={0}; while(1) { confd=accept(listfd,(struct sockaddr *)&clientaddr,&clilen); //获取来自客户端连接的信息,返回值为他的描述符 if(confd == -1) { perror("accept"); return -1; } read(confd,buf,sizeof(buf)); printf("buf = %s\n",buf); printf("addr = %sport = %d\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port)); //打印连接客户端的信息 } clost(listfd); return 0; }

其他函数
int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen); //获取某个套接口关联的本地协议地址 错误返回-1 int getsockopt(int sockfd,int level,int optname ,void *optavl,socklen_t *optlen); int setsockpt(int sockfd ,int level,int optname,const void *optval,socklen_t optlen); //上面两个函数一个是设置,一个是获取信息,用来设置socket的参数后信息,感兴趣的同学可以上网查查都有哪些可以设置的选项,这里列举一下允许发送广播的设置 setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(void *)&val,sizeof(val)); //允许发送广播 //设置广播组: struct ip_mreqn mry; memest(&mrq,0,sizeof(mrq)); mry.imr_multiaddr.s_addr =inte_addr(224.20.10.1"); mry.imr_address.s_addr = htonl(INADDR_ANY): setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mry,sizeof(mry)); //设置一个广播组,在组内的人都可以接收到发送的信息。

udp客户端
#include #include #include #include #include #include #include int main() { char buf[1024]={0}; struct sockaddr_inaddr; memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(8899); addr.sin_addr.s_addr = inet_addr("10.32.56.192"); int sockfd = socket(AF_INET,SOCK_DGRAM,0); //和TCP中的socket设置稍微有一点不同 if(sockfd == -1) { perror("select"); return -1; }while(1) { memset(buf,0,sizeof(buf)); scanf("%s",buf); sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&addr,sizeof(addr)); //直接发送,不需要connect(); 连接服务器。 } return 0; } ~

udp服务端
#include #include #include int main() { struct sockaddr_in saddr; //服务端地址 struct sockaddr_in caddr; //客户端信息 char buf[1024]={0}; int clent = sizeof(caddr); //客户端地址大小 memset(&saddr,0,sizeof(saddr)); memset(&caddr,0,sizeof(caddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(8899); saddr.sin_addr.s_addr = htonl(INADDR_ANY); int confd = socket(AF_INET,SOCK_DGRAM,0); //设置socket描述符 if(confd == -1) { perror("select"); return -1; } if(bind(confd,(struct sockaddr *)&saddr,sizeof(saddr))==-1)//绑定地址 { perror("bind"); return -1; } while(1) { recvfrom(confd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,&clent); printf("%s \n",buf); //接收打印 sendto(confd,"ok",2,0,(struct sockaddr *)&caddr,sizeof(caddr)); // memset(buf,0,sizeof(buf)); }return 0; }

总结 以上就是今天所要分享的代码了
学习路漫漫,我们仍需负重前行……
为此博主新创建了一个群,正等待你们的加入:
QQ群:928357277

上一章:纯新多线程【点击学习】

    推荐阅读