TCP/IP(7)-TCP Server与TCP Client(linux套接字)
来源:互联网 发布:战舰少女程序员 编辑:程序博客网 时间:2024/05/22 00:34
前面几篇文章谈到的关于TCP/IP应用层以下的协议,这些协议最终是在操作系统内核中实现的,套接字API是unix系统用于网络连接的接口,后来被移植到windows系统中,就有了winsock。
TCP的Client/Server模式
在TCP/IP协议中已经讲解了TCP协议中三次握手和四次握手过程,以及发送消息和接受消息。那么在linux系统中,内核中已经将这些协议实现,现在我们一起看看linux下套接字编程的API。
TCP服务器端
1. 创建套接字
#include <sys/socket.h> int socket(int family,int type,int protocol); 返回:非负描述字---成功 -1---失败
第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。
2.绑定套接字
把一个套接字地址(本机IP和端口号)绑定到创建的套接字上。绑定套接字时可以选择指定IP地址和端口,也可以不指定。通配的IP地址用INADDR_ANY表示,通配的端口用0表示,通配的情况下由内核为其指定相应的IP地址和端口号。
对于客户端可以绑定套接字,但是一般不需要,因为客户端的端口号只是临时的,由内核来分配更合理。但是对服务器而言,一般要使用知名端口号,如果不进行绑定,客户端不知道目的端口号,连接不能完成。
通配地址实现:htonl(INADDR_ANY)
通配地址,内核将等到套接字已连接TCP或已经发出数据报(UDP)时才指定。
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen); 返回:0---成功 -1---失败
3.监听
socket创建的套接字是主动套接字,调用listen后变成监听套接字。TCP状态有CLOSE跃迁到LISTEN状态。
backlog是已完成队列和未完成队列大小之和,对于监听套接字有两个队列,一个是未完成队列,一个是已完成队列。
- 未完成队列:客户端发送一个SYN包,服务器收到后变成SYN_RCVD状态,这样的套接字被加入到未完成队列中。
- 已完成队列:TCP已经完成了3次握手后,将这个套接字加入到已完成队列,套接字处于ESTABLISHED状态。
下图中可以看出,TCP的三次握手是在调用connect函数时完成的,服务器端没有调用函数,但是必须有套接字在某个端口监听,不然会返回客户端RST,终止连接。
#include<sys/socket.h>int listen(int sockfd, int backlog);
调用listen函数后的套接字称为监听套接字。
4.accept函数
accept函数从已完成连接的队列中取走一个套接字,如果该队列为空,则accept函数阻塞。accept函数的返回值称为已连接套接字,已连接的套接字就建立一个完整的TCP连接,源IP地址,源端口号,目的IP地址,目的端口号都是唯一确定了。
#include <sys/socket.h> int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen);
5.数据传输
- write和read函数:当服务器和客户端的连接建立起来后,就可以进行数据传输了,服务器和客户端用各自的套接字描述符进行读/写操作。因为套接字描述符也是一种文件描述符,所以可以用文件读/写函数write()和read()进行接收和发送操作。
write()函数用于数据的发送
#include <unistd.h> int write(int sockfd, char *buf, int len); 回:非负---成功 -1---失败
参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符;参数buf是指向一个用于发送信息的数据缓冲区;len指明传送数据缓冲区的大小。
read()函数用于数据的接收
#include <unistd.h> int read(int sockfd, char *buf, intlen); 回:非负---成功 -1---失败
参数sockfd是套接字描述符,对于服务器是accept()函数返回的已连接套接字描述符,对于客户端是调用socket()函数返回的套接字描述符;参数buf是指向一个用于接收信息的数据缓冲区;len指明接收数据缓冲区的大小。
- send和recv函数:TCP套接字提供了send()和recv()函数,用来发送和接收操作。这两个函数与write()和read()函数很相似,只是多了一个附加的参数。
(1)send()函数用于数据的发送。
#include <sys/types.h>#include < sys/socket.h > ssize_t send(int sockfd, const void *buf, size_t len, int flags); 回:返回写出的字节数---成功 -1---失败
前3个参数与write()相同,参数flags是传输控制标志。
(2)recv()函数用于数据的发送。
#include <sys/types.h>#include < sys/socket.h > ssize_t recv(int sockfd, void *buf, size_t len, int flags); 回:返回读入的字节数---成功 -1---失败
前3个参数与read()相同,参数flags是传输控制标志。
6.关闭套接字
close函数关闭套接字
#include <unistd.h>int close(int sockfd);
TCP客户端
1.创建套接字
2.连接服务器
TCP用connect函数来建立与TCP服务器的连接。
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen); 返回:0---成功 -1---失败
客户端发送的SYN包可能会遇到失败,可能有以下几种情况:
1. 如果客户端没有收到SYN的响应包,根据TCP的超时重发机制进行重发。75秒后还没收到,就返回错误。
2. 如果目的主机没有监听目的端口号,就会返回一个RST的分节,客户端收到RST后立刻返回错误。
3. 如果SYN在中间路由遇到目的不可达,客户端收到ICMP报文,客户端保存这个报文信息,并采用第一种情况方案解决,也就是重发。
3.收发数据
4.关闭套接字
TCP聊天室服务端程序
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/types.h>#include <unistd.h>#define BUFLEN 100const char* IP = "127.0.0.1";const unsigned int SERV_PORT = 7777;void Chat(int sockfd);int main(int argc, char *argv[]){ int listenfd, connectfd; struct sockaddr_in s_addr, c_addr; char buf[BUFLEN]; unsigned int port, listnum; pid_t childpid; socklen_t len; /*建立socket*/ if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ perror("socket"); exit(errno); } /*设置服务器端口*/ port = SERV_PORT; /*设置侦听队列长度*/ listnum = 5; /*设置服务器ip*/ bzero(&s_addr, sizeof(s_addr)); s_addr.sin_family = AF_INET; s_addr.sin_port = htons(port);// s_addr.sin_addr.s_addr = inet_aton(IP, &s_addr.sin_addr); s_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*把地址和端口帮定到套接字上*/ if((bind(listenfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){ perror("bind"); exit(errno); } /*侦听本地端口*/ if(listen(listenfd, listnum) == -1){ perror("listen"); exit(errno); } while(1){ printf("*****************server start***************\n"); len = sizeof(struct sockaddr); if((connectfd = accept(listenfd, (struct sockaddr*) &c_addr, &len)) == -1){ perror("accept"); exit(errno); } else { printf("connected with client, IP is: %s, PORT is: %d\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port)); } //创建子进程 if((childpid = fork()) == 0) { Chat(connectfd); /*关闭已连接套接字*/ close(connectfd); /*是否退出服务器*/ printf("exit?:y->yes;n->no "); bzero(buf, BUFLEN); fgets(buf,BUFLEN, stdin); if(!strncasecmp(buf,"y",1)){ printf("server stop\n"); break; } //退出子进程 exit(0); } } /*关闭监听的套接字*/ close(listenfd); return 0;}void Chat(int sockfd){ socklen_t len; char buf[BUFLEN]; while(1) { _retry: /******发送消息*******/ bzero(buf,BUFLEN); printf("enter your words:"); /*fgets函数:从流中读取BUFLEN-1个字符*/ fgets(buf,BUFLEN,stdin); /*打印发送的消息*/ //fputs(buf,stdout); if(!strncasecmp(buf,"quit",4)) { printf("server stop\n"); break; } /*如果输入的字符串只有"\n",即回车,那么请重新输入*/ if(!strncmp(buf,"\n",1)) { goto _retry; } /*如果buf中含有'\n',那么要用strlen(buf)-1,去掉'\n'*/ if(strchr(buf,'\n')) { len = send(sockfd, buf,strlen(buf)-1,0); } /*如果buf中没有'\n',则用buf的真正长度strlen(buf)*/ else { len = send(sockfd,buf,strlen(buf),0); } if(len > 0) printf("send successful\n"); else{ printf("send failed\n"); break; } /******接收消息*******/ bzero(buf,BUFLEN); len = recv(sockfd,buf,BUFLEN,0); if(len > 0) printf("receive massage:%s\n",buf); else { if(len < 0 ) printf("receive failed\n"); else//服务器调用close函数后,系统阻塞函数调用,返回0 printf("client stop\n"); break; } }}
TCP聊天室客户端程序
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/types.h>#include <unistd.h>#define BUFLEN 100const char* IP = "127.0.0.1";const int SERV_PORT = 7777;int main(int argc, char *argv[]){ int sockfd; struct sockaddr_in s_addr; socklen_t len; unsigned int port; char buf[BUFLEN]; /*建立socket*/ if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ perror("socket"); exit(errno); } /*设置服务器端口*/ port = SERV_PORT; /*设置服务器ip*/ bzero(&s_addr, sizeof(s_addr)); s_addr.sin_family = AF_INET; s_addr.sin_port = htons(port); if(inet_aton(IP, (struct in_addr*)&s_addr.sin_addr.s_addr) == 0){ perror("IP error"); exit(errno); } /*开始连接服务器*/ if(connect(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr)) == -1){ perror("connect"); exit(errno); }else printf("*****************client start***************\n"); while(1){ /******接收消息*******/ bzero(buf,BUFLEN); len = recv(sockfd,buf,BUFLEN,0); if(len > 0) printf("receive massage:%s\n",buf); else{ if(len < 0 ) printf("receive failed\n"); else printf("server stop\n"); break; } _retry: /******发送消息*******/ bzero(buf,BUFLEN); printf("enter your words:"); /*fgets函数:从流中读取BUFLEN-1个字符*/ fgets(buf,BUFLEN,stdin); /*打印发送的消息*/ //fputs(buf,stdout); if(!strncasecmp(buf,"quit",4)){ printf("client stop\n"); break; } /*如果输入的字符串只有"\n",即回车,那么请重新输入*/ if(!strncmp(buf,"\n",1)){ goto _retry; } /*如果buf中含有'\n',那么要用strlen(buf)-1,去掉'\n'*/ if(strchr(buf,'\n')) len = send(sockfd,buf,strlen(buf)-1,0); /*如果buf中没有'\n',则用buf的真正长度strlen(buf)*/ else len = send(sockfd,buf,strlen(buf),0); if(len > 0) printf("send successful\n"); else{ printf("send failed\n"); break; } } /*关闭连接*/ close(sockfd); return 0;}
- TCP/IP(7)-TCP Server与TCP Client(linux套接字)
- TCP/IP(8)-UDP Server与UDP Client(linux套接字)
- TCP套接字(server/client实现)
- linux tcp server client
- linux Tcp 套接字
- 网络套接字socket(tcp、server、client)
- TCP/IP 套接字,OAuth
- 使用TCP/ IP套接字
- Tcp Client与Server 基本原理
- android tcp/ip server接多个client
- Linux套接字(TCP篇)
- linux TCP套接字编程
- Linux TCP套接字编程
- linux程序设计(套接字)+TCP/IP网络编程学习笔记
- linux网络编程之套接字/TCP/IP
- TCP/IP“优雅地”打开与断开套接字。
- java Tcp Client server
- TCP:Server-Client程序
- PHP Class中public,private,protected,static的区别
- DFS
- 数据库练习题笔记
- 程序员面试经典--链表分割
- Linux认证失败问题解决
- TCP/IP(7)-TCP Server与TCP Client(linux套接字)
- Oracle入门
- [网络流24题]骑士共存问题
- Leetcode-557. Reverse Words in a String III
- iTerm2文件夹显示颜色
- DP —> 背包问题
- 使用ReentrantLock实现线程同步
- 位运算
- 浅谈数据库锁机制