目录
TCP介绍
C/S模型框架
Server端
1.创建套接字
2.绑定IP地址和端口号
3.使用该套接字监听连接请求
4.当请求来到时,调用accept函数复制该套接字处理请求
5.数据通信
client端
1.创建套接字
2.使用创建好的套接字向服务端发送连接请求
3.利用套接字进行数据的通信
server端简单示例代码
client端简单示例代码
存在的问题
TCP:传输控制协议,提供面向连接的,一对一的可靠数据传输协议。
TCP特点:能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。
TCP适用情况:
int socket(int domain, int type, int protocol);
该套接字是后面用来监听客户端请求的套接字。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
该函数在填写addr参数时,要注意,我们先填写struct sockaddr_in这个结构体,再传递参数时,要将sockaddr_in类型的指针强转成sockaddr类型的指针。
注意:网络字节序和本地字节序的切换。
端口:
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
ip地址:
in_addr_t inet_addr(const char *cp);
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
int listen(int sockfd, int backlog);
socket创建的套接字是一个主动套接字,也就是说是主动连接别人的一个套接字。listen函数将一个未连接的套接字转换成了一个被动套接字,等待连接。调用listen使套接字从closed状态转换成了listen状态。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
一般我们将函数第一个参数称为监听套接字,将函数返回的套接字称为已连接套接字。一个服务器只有一个监听套接字,监听客户端的连接请求。服务器内核为每一个客户端的TCP连接维护1个已连接套接字,用它实现数据双向通信。
利用accept返回的套接字,进行数据的通信。也就是IO操作了。
同TCP
该套接字是用来向服务器发送请求的套接字。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数第二个参数是一个结构体,该结构体中保存着发送连接请求对象的ip地址和端口号。
int main(int argc, const char *argv[]) { int fd = -1; struct sockaddr_in sin; int ret ; /*1.创建socket套接子*/ if((fd = socket(AF_INET,SOCK_STREAM,0)) == -1) { perror("socket"); exit(1); } /*2.bind绑定,绑定之前填充sockaddr_in这个结构体*/ /*2.1 sin_zero 后八位要全部置零*/ bzero(&sin,sizeof(sin)); /*2.2 域指定IPV4编程*/ sin.sin_family = AF_INET; /*2.3 填写端口号*/ sin.sin_port = htons(5001); /*2.4 将网络地址填写到sin.sin_addr.s_addr中*/ sin.sin_addr.s_addr = inet_addr("192.168.1.3"); /*inet_addr函数只适用于IPV4*/ #if 0 inet_pton(AF_INET,"127.0.0.1",(void *)&sin.sin_addr.s_addr);/*inet_pton可以使用IPV6*/ #endif #if 0 sin.sin_addr.s_addr = htonl(INADDR_ANY); /*只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。*/ #endif /*2.5 最后进行绑定*/ if(bind(fd,(struct sockaddr*)&sin,sizeof(sin)) < 0) { perror("bind"); exit(1); } /*3.listen 把主动套接子变成被动套接子*/ if(listen(fd,5) < 0) { perror("listen"); exit(1); } /*4.阻塞等待客户端链接请求*/ int newfd = -1; newfd = accept(fd,NULL,NULL); /*两个NULL 不关心客户端的ip和端口号,所以就NULL*/ if(newfd < 0) { perror("accept"); exit(1); } /*5.读写*/ ret = -1; char buf[255]; while(1) { bzero(buf,255); do { ret = read(newfd,buf,255 - 1); }while(ret < 0 && errno != EINTR); if(ret < 0) { perror("read"); exit(1); } if(!ret) //对方以关闭 { break; } printf("Receive data :%s\n",buf); if(!strncasecmp(buf,"quit",4)) { break; } } close(newfd); close(fd); return 0; }
#include <stdio.h> #include <unistd.h> #include <string.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <errno.h> int main(int argc, const char *argv[]) { int fd = -1; struct sockaddr_in sin; int ret ; /*1.创建socket套接子*/ if((fd = socket(AF_INET,SOCK_STREAM,0)) == -1) { perror("socket"); exit(1); } /*2.链接服务器*/ bzero(&sin,sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(5001); sin.sin_addr.s_addr = inet_addr("192.168.1.3"); /*只适用于IPV4*/ /*inet_pton(AF_INET,"127.0.0.1",(void *)&sin.sin_addr.s_addr);*/ /*最后进行发送连接请求*/ if(connect(fd,(struct sockaddr*)&sin,sizeof(sin)) < 0) { perror("connect"); exit(1); } /*3.读写*/ ret = -1; char buf[255]; while(1) { bzero(buf,255); if(fgets(buf,256,stdin) == NULL) { continue; } ret = write(fd,buf,strlen(buf)); if(!strncasecmp(buf,"quit",4)) { break; } } /*关闭套接字*/ close(fd); return 0; }
在服务端read函数处是具有一个阻塞功能,当一个客户端连接服务器,服务器进行读的处理,程序一直阻塞在read处,如果又有一个客户端请求连接服务器,这时的服务器accept函数没有执行,所以不能连接心的客户端。这就意味着不能实现并发。改进方法,可以使用多线程,多进程编程等方法解决。