目录
一.基于TCP协议的网络点对点通信的基本过程
二.介绍上述过程中涉及的函数
三.服务器端的实现代码
一.基于TCP协议的网络点对点通信的基本过程
二.介绍上述过程中涉及的函数
库 | 函数原型 | 解释 | 作用 | <sys/socket.h> | int socket(int domain, int type, int protocol); | domain:该套接字所遵循的IP协议,AF_INET或是AF_INET6 type:套接字的种类,分为流套接字SOCK_STREAM和报套接字SOCK_DGRAM protocol:对应套接字种类的代表协议(流套接字的代表协议为TCP协议,报套接字的代表协议为UDP协议),填0即可。(或TCP协议填) return:返回一个文件描述符 | socket() creates an endpoint for communication and returns a descriptor. | ~ | int bind(int sockfd, const struct sockaddr *addr,? socklen_t addrlen); | sockfd: socket函数返回的文件描述符 sockaddr: 套接字地址 socklen_t: 套接字地址长度 return: 0成功-1失败 | socket()只是创建了一个文件标识符fd指定的套接字,但是其并没有地址。bind()函数为其绑定了一个套接字地址。 | ~ | int connect(int sockfd, const struct sockaddr *addr,?socklen_t addrlen); | sockfd:客户端用于通信的套接字的文件描述符。 addr:传入参数。表示服务器端的套接字地址。(这结合功能很好理解) addrlen:服务器套接字地址的长度(单位:字节) return:0成功-1失败 | 对于TCP而言,connect函数进行一次连接尝试(三次握手),成功或者失败后返回。注意,不是到成功为止。 由客户端调用。 注意,connect函数只是发起三次握手请求,其需要和accept配合,实现三次握手。 | ~ | int listen(int sockfd, int backlog); | sockfd:主动状态下套接字的文件描述符。 backlog:服务器的监听上限。服务器有一个队列存放待处理的连接(三次握手)请求,backlog就是这个队列的长度(以一个请求为单位)。 return:0成功-1失败 | 将sockfd标识的套接字的状态由主动套接字转变为被动套接字。并且为服务器设置最大的监听上限。 | ~ | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); | sockfd:服务器端的被动套接字 addr:传出参数。客户端的套接字地址。 addrlen:传入传输参数。我们需要一个buffer存放客户端套接字地址。我们把sizeof(buffer)传入,accept会传出精确的客户端套接字的长度。 return:返回一个连接套接字的文件描述符。 | 若存放连接的队列为空,accept则进行阻塞监听;不为空,accept则取出队首,将客户端套接字的地址存入addr指定的内存单元中,并且释放掉监听套接字,返回一个连接套接字,其和监听套接字的原地址绑定。 | #include <unistd.h> | ?int close(int fd); | fd:指定的文件描述符 return:0成功,-1失败 | ?close() ?closes ?a ?file ?descriptor, ?so that it no longer refers to any file and may be reused. | 1.我们发现在基于TCP协议的通信过程中,服务器端为socket函数产生的套接字(实质上是一个文件描述符)绑定了一个套接字地址,但是客户端并没有这么做。这是因为在客户端调用connect函数时,系统为其套接字自动分配了一个套接字地址。 2.套接字的三种状态(very important):文件描述符状态。就是刚刚被socket创建,还未与套接字地址绑定时的状态。? ? ? ? 关闭状态或称被动状态。文件描述符状态的套接字,在经过bind函数绑定一个套接字地址之后,就进入关闭状态。关闭状态的套接字为主动套接字,即客户端的套接字,将调用connect函数主动寻求和服务器连接。? ? ? ? ?监听状态或称被动状态。主动状态的套接字调用listen函数后变为服务器端的套接字,将调用accept函数进行监听阻塞。 3.更详细的信息需要在Linux的手册中看。命令诸如man listen等等。 4.区分服务器端的监听套接字和连接套接字。监听套接字是由socket函数创建的,作为bind函数,listen函数和accept参数的那个套接字,其作用仅仅是用于引导客户端的套接字和服务器端的连接套接字组成socket pair,建立TCP连接。? ? ? ? 连接套接字是由accept函数返回的那个文件描述指定的,是真正与客户端建立连接的套接字。? ? ? ? 服务器端往往只有一个监听套接字,其被反复使用,用于引导建立连接。 |
三.服务器端的实现代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#define SERV_PORT 9527
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//用于抛出错误
void sys_err(const char *str){
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int sfd = socket(AF_INET,SOCK_STREAM,0);//套接字文件描述符--监听套接字
int cfd = -1;//通信套接字
struct sockaddr_in serv_addr; //服务器端套接字地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT) ;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr_in clnt_addr;
socklen_t lgth_clnt_addr = sizeof(clnt_addr);
int rec = 0;//read到的字节数
char buf[BUFSIZ]; //BUFSIZ是系统预定义的,为4096
int i = 0;
if(sfd == -1){
sys_err("fail to create a socket!");
}
if((bind(sfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr))) == -1){
sys_err("fail to bind a socket!");
}
if(listen(sfd,128) == -1){
sys_err("fail to listen!");
}
cfd = accept(sfd,(struct sockaddr*)&clnt_addr,&lgth_clnt_addr);
if(cfd == -1){
sys_err("fail to accept!");
}
//既然成功连接了客户端,咱打印一下
printf("The IP address of the client is %s.\n",inet_ntop(AF_INET,&clnt_addr.sin_addr.s_addr,clnt_ip_addr,sizeof(clnt_ip_addr)));
printf("The port of the client is %d.\n",ntohs(clnt_addr.sin_port));
while(1){
rec = read(cfd,buf,sizeof(buf));//这里涉及到数组的大小是多少,为整个数组的内存大小
write(STDOUT_FILENO,buf,rec);//这步是向屏幕输出接收到的字母序列,STDOUT_FILENO是屏幕的文件标识符
for(i = 0;i < rec;++i){
buf[i] = toupper(buf[i]);
}
write(cfd,buf,rec);
}
close(sfd);
close(cfd);
return 0;
}
下面是几个注意点:
1.sockaddr和sockaddr_in的库,socket相关函数的库,字节序转换库<arpa/inet.h>不要忘记include。
2.sfd和cfd最好分别定义一个。尽管可以通过sfd = accept(sfd...),但是这样的话在最后close时,只能关闭通信套接字,释放不了监听套接字。而且当需要建立两个连接时,监听套接字不能被复用。
3.Linux的gcc编译器可能比较老,不支持C99语法,结构体前面必需加struct,否则会报错。
4.sockaddr到sockaddrd_in的强制类型转换不要忘记了。
5.每一步的容错不要懒得写,等到连接失败的时候很容易不知道哪步出错。
下面是不写客户端就可以测试的方法。先用XShell打开另外一个Linux命令行,执行nc 127.0.0.1 9527命令,9527是我们赋给SERV_PORT常量的值,也可以是其他的,不过代码就要相应地做出更改。这样就可以直接在命令行中输入小写字母序列,命令行会打印对应的大写字母。
?
?上面边是服务器端,下面是客户端。
|