赞
踩
前天面试了环信公司,在面试的时候,感觉面试官很亲切,临走的时候叫我回去再把TCP和UDP网络编程在看看,并且给他发过去,我知道现在的水平确实还有很大提升的空间,我也会一直不断的学习,加强自身的知识水平建设。
(1)基于UDP协议的socket套接字编程
UDP协议是非链接的协议,它不与对方建立连接,而是直接把要发送的数据发送给对方。所以UDP协议适用于一次传输数据量很少,对可靠性要求不高的应用场景。但是正是因为udp协议没有类似于tcp的三次握手,可靠传输机制等,所以效率比较高。
UDP协议的应用也非常广泛,比如知名的应用层协议:SNMP,DNS都是基于UDP的。
UDP通信的流程比较简单,因此要搭建这么一个常用的UDP通信框架也是比较简单的。以下是UDP的框架图。
由以上框图可以看出,客户端要发起一次请求,仅仅需要个步骤(socket和sendto),而服务器端也仅仅需要三个步骤即可接收到来自客户端的消息(socket、bind、recvfrom)。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain 如果是IPv4就写AF_INET,如果是IPV6就是AF_INET6.
type 如果是TCP就写SOCK_STREAM,UDP就写SOCK_DGRAM。
protocol 填0就可以了。因为通过前面两个参数一般就可以确定后面的这个是什么类型,前面填了AF_INET,SOCK_DGRAM 适用的就是UDP了。
-2. bind()函数
这里服务器调用就可以了,将本机IP地址,选定的一个端口与前面的套接字绑定。客户端的话,就可以不用了。
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
如果bind()返回成功,就会返回0。
如果返回不成功,可以调用GetLastError() 函数,通过错误编码查看是什么错误。
sendto(),recvfrom()
.int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
以下是UDP网络编程的一个小栗子。
server.c #include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<stdlib.h> #include<string.h> #include<arpa/inet.h> //服务器程序,./server [ip] [port] // ./server 127.0.0.1 9090 int main(int argc,const char* argv[]) { if(argc < 3) { printf("input error!\n"); return 1; } int fd = socket(AF_INET,SOCK_DGRAM,0); if(fd < 0) { perror("socket\n"); return 2; } struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(atoi(argv[2])); server_addr.sin_addr.s_addr = inet_addr(argv[1]); int bd = bind(fd,(struct sockaddr*)&server_addr,sizeof(server_addr)); if(bd < 0) { perror("bind\n"); return 3; } char buf[1024] = {0}; struct sockaddr_in client_addr; while(1) { socklen_t len = sizeof(client_addr); ssize_t s = recvfrom(fd,buf,sizeof(buf)-1,0,(struct sockaddr*)&client_addr,&len); if(s < 0) { perror("recvfrom"); continue; } buf[s] = '\n'; printf("[%s]: [%d]: %s",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf); sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&client_addr,sizeof(client_addr)); } close(fd); return 0; } / client.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<unistd.h> // ./client 127.0.0.1 9090 int main(int argc,const char* argv[]) { int fd = socket(AF_INET,SOCK_DGRAM,0); if(fd < 0) { perror("socket\n"); return 1; } struct sockaddr_in server; bzero(&server, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); char buf[1024] = {0}; struct sockaddr_in peer; while(1) { socklen_t len = sizeof(peer); printf(">>>"); fflush(stdout); ssize_t s = read(0,buf,sizeof(buf)-1); if(s < 0) { perror("read\n"); return 1; } buf[s-1] = '\n'; sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server)); ssize_t rc = recvfrom(fd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len); if(rc > 0) { buf[rc] = '\n'; printf("server echo: %s\n",buf); } } close(fd); return 0; }
TCP套接字编程
主要函数:
1.创建套接字
int socket(int domain,int type,int protocol);
//domain:该参数一般被设置为AF_INET,表示使用的是IPv4地址。还有更多选项可以利用man查看该函数
//type:该参数也有很多选项,例如SOCK_STREAM表示面向流的传输协议,SOCK_DGRAM表示数据报,我们这里实现的是TCP,因此选用SOCK_STREAM,如果实现UDP可选SOCK_DGRAM
//protocol:协议类型,一般使用默认,设置为0
2.绑定
int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
//sockfd:服务器打开的sock
//后两个参数可以参考第四部分的介绍
3.监听
int listen(int sockfd,int backlog);
//sockfd的含义与bind中的相同。
//backlog参数解释为内核为次套接口排队的最大数量,这个大小一般为5~10,不宜太大(是为了防止SYN攻击)
4.接受连接
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
//addrlen是一个传入传出型参数,传入的是调用者的缓冲区cliaddr的长度,以避免缓冲区溢出问题;传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给cliaddr参数传NULL,表示不关心客户端的地址。
典型的服务器程序是可以同时服务多个客户端的,当有客户端发起连接时,服务器就调用accept()返回并接收这个连接,如果有大量客户端发起请求,服务器来不及处理,还没有accept的客户端就处于连接等待状态。
三次握手完成后,服务器调用accept()接收连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
5、请求连接
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
TCP连接的基本流程:
服务器:首先调用socket()创建一个套接字用来通讯,其次调用bind()进行绑定这个文件描述符,并调用listen()用来监听端口是否有客户端请求来,如果有,就调用accept()进行连接,否则就继续阻塞式等待直到有客户端连接上来。连接建立后就可以进行通信了。
客户端:调用socket()分配一个用来通讯的端口,接着就调用connect()发出SYN请求并处于阻塞等待服务器应答状态,服务器应答一个SYN-ACK分段,客户端收到后从connect()返回,同时应答一个ACK分段,服务器收到后从accept()返回,连接建立成功。客户端一般不调用bind()来绑定一个端口号,并不是不允许bind(),服务器也不是必须要bind()。
为什么不建议客户端进行bind()?
答:当客户端没有自己进行bind时,系统随机分配给客户端一个端口号,并且在分配的时候,操作系统会做到不与现有的端口号发生冲突。但如果自己进行bind,客户端程序就很容易出现问题,假设在一个PC机上开启多个客户端进程,如果是用户自己绑定了端口号,必然会造成端口冲突,影响通信。
TCP通信的一个例子:
//server.c #include<stdio.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include<stdlib.h> #include<arpa/inet.h> #include<string.h> // ./server 127.0.0.1 8080 int main(int argc,char* argv[]) { if(argc != 3) { printf("Usage: ./server [ip] [port]\n"); return 1; } int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0) { perror("sock"); return 1; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); int bd = bind(sockfd,(struct sockaddr*)&server,sizeof(server)); if(bd < 0) { perror("bind"); return 1; } int ls = listen(sockfd,5); if(ls < 0) { perror("listen"); return 1; } while(1) { struct sockaddr_in client; socklen_t len = sizeof(client); int new_fd = accept(sockfd,(struct sockaddr*)&client,&len); if(new_fd < 0) { perror("accept"); continue; } while(1) { char buf[1024] = {0}; ssize_t read_size = read(new_fd,buf,sizeof(buf)-1); if(read_size < 0) { perror("read"); continue; } if(read_size == 0) { printf("client close!\n"); break; } buf[read_size] = '\0'; printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf); write(new_fd,buf,strlen(buf)); } close(new_fd); } return 0; } / //client.c #include<stdio.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> int main(int argc,char* argv[]) { if(argc != 3) { printf("Usage: ./client [ip] [port]\n"); return 1; } int fd = socket(AF_INET,SOCK_STREAM,0); if(fd < 0) { perror("socket"); return 1; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); int ret = connect(fd,(struct sockaddr*)&server,sizeof(server)); if(ret < 0) { perror("connect"); return 1; }else { printf("connect success!\n"); } while(1) { char buf[1024] = {0}; printf(">>>"); fflush(stdout); int read_size = read(0,buf,sizeof(buf)-1); if(read_size < 0) { perror("read"); continue; } if(read_size == 0) { printf("goodbye\n"); break; } buf[read_size] = '\0'; write(fd,buf,strlen(buf)); char buf_output[1024] = {0}; ssize_t size = read(fd,buf_output,sizeof(buf_output)-1); if(size < 0) { perror("read"); continue; } if(size ==0) { printf("goodbye!"); break; } buf_output[size] = '\0'; printf("%s\n",buf_output); } close(fd); return 0; }
**一般而言,UDP和TCP编程会使用到的基本函数。**
// 创建socket 文件描述符(TCP/UDP, 客户端+ 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号(TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求(TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接(TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。