TCP客户/服务器模型:
从图中就可以看出基本的过程来。
回射客户/服务器:
这个是回射的图示,客户端发给服务端,服务端在发回给客户端。
socket函数:
包含头文件<sys/socket.h>
功能:创建一个套接字用于通信
原型
int socket(int domain, int type, int protocol);
参数
domain :指定通信协议族(protocol family)
type:指定socket类型,流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol :协议类型
返回值:成功返回非负整数, 它与文件描述符类似,我们把它称为套接口描述字,简称套接字。失败返回-1
bind函数:
包含头文件<sys/socket.h>
功能:绑定一个本地地址到套接字
原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd:socket函数返回的套接字
addr:要绑定的地址
addrlen:地址长度
返回值:成功返回0,失败返回-1
listen函数:
包含头文件<sys/socket.h>
功能:将套接字用于监听进入的连接
原型
int listen(int sockfd, int backlog);
参数
sockfd:socket函数返回的套接字
backlog:规定内核为此套接字排队的最大连接个数
返回值:成功返回0,失败返回-1
listen函数:
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
2、已完成连接的队列
accept函数:
包含头文件<sys/socket.h>
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
sockfd:服务器套接字
addr:将返回对等方的套接字地址
addrlen:返回对等方的套接字地址长度
返回值:成功返回非负整数,失败返回-1
connect函数:
包含头文件<sys/socket.h>
功能:建立一个连接至addr所指定的套接字
原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd:未连接套接字
addr:要连接的套接字地址
addrlen:第二个参数addr长度
返回值:成功返回0,失败返回-1
下面是回射的小程序,其中有很多点需要注意:
服务器端:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(void) { int listenfd; if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)//第三个参数可以指定,也可以为0,第一个参数可以AF_INET也可以用PF这个,建议用PF /* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/ ERR_EXIT("socket"); struct sockaddr_in servaddr;//注意一下这个结构,这个和bind中的通用的结构不一样,用的时候需要转换成通用结构 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//可以是任意,一般用这种 /*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/ //也可以指定 /*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); if (listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("listen"); struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn; if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)//注意这里的后两个参数是返回客服端的信息的,并且返回的是客户端的套接字 ERR_EXIT("accept"); char recvbuf[1024]; while (1) //再就是注意缓冲区的清空,不然数据会不正常 { memset(recvbuf, 0, sizeof(recvbuf)); int ret = read(conn, recvbuf, sizeof(recvbuf)); fputs(recvbuf, stdout); write(conn, recvbuf, ret); } close(conn); close(listenfd); return 0; }
客户端:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(void) { int sock; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //客户端这里需要指定,不能跟服务器端的那样 if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); char sendbuf[1024] = {0}; char recvbuf[1024] ={0}; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { write(sock, sendbuf, strlen(sendbuf)); read(sock, recvbuf, sizeof(recvbuf)); fputs(recvbuf, stdout); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close(sock); return 0; }
大家体会一下吧。