1.1 TCP服务器端的默认函数调用顺序:
上一章讲解了:
- 调用socket函数创建套接字
- 声明并初始化地址信息结构体变量
- 调用bind函数向套接字分配地址
下面将讲解之后的几个过程
1.2 进入等待连接请求状态
#include <sys/socket.h>
int listen(int sock, int backlog); // 成功返回0,失败返回-1
sock:希望进入等待连接请求状态的套接字文件描述符,成为服务器端套接字(监听套接字)
backlog:连接请求等待队列(Queue)的长度。
1.3 受理客户端连接请求
#include <sys/socket.h>
int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);
// 成功返回创建的套接字文件描述符,失败返回-1;
sock:服务器套接字的文件描述符
addr:保存发起连接请求的客户端的地址信息(可选参数,一般直接填写为NULL即可)
addrlen:客户端地址长度(可选参数,一般直接填写为NULL即可)
调用accept将从连接请求等待队列中取出一个已经通过三次握手建立连接的文件描述符,
若等待队列为空,则进入阻塞状态,直至有连接进入队列。
2.1 TCP客户端默认函数调用顺序
客户端与服务器端的区别就是没有listen和accept,但是有一个用于请求连接的connect
#include <sys/socket.h>
int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);
// 成功返回0,失败返回-1
sock:客户端套接字文件描述符
servaddr:目标服务器地址信息
addrlen:第二个参数的长度
调用connect后进入阻塞状态,当三次握手完成,即服务器将该连接请求记录到等待队列后,
connect返回。因为此时服务器端可能还没有执行到accept,所以并不立即进行数据交换。
问:客户端的IP地址和端口何时分配?
答:客户端的IP地址和端口在调用connect函数时自动分配,IP为当前计算机的IP地址,端口随机。
3.1 基于TCP的服务器端/客户端函数调用关系
3.2 迭代回声服务器/客户端
服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void err_sys(char *message);
int main(int argc, char* argv[])
{
int listenfd, connfd; // socket文件描述符
char msgbuf[BUF_SIZE];
int str_len, i;
struct sockaddr_in serv_addr, clnt_addr; //套接字地址结构
socklen_t clnt_adr_sz;
if(argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
// 调用socket()创建套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if( listenfd == -1)
err_sys("socket() error");
// 向sockaddr_in套接字地址结构体填充信息
memset(&serv_addr, 0, sizeof(serv_addr)); // 清零 因为sin_zero[8]必须清零
serv_addr.sin_family = AF_INET; // 填充协议族
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 填充IP地址
serv_addr.sin_port = htons(atoi(argv[1])); // 填充端口号
// 调用bind()向套接字分配填充好的地址信息
if( bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
err_sys("bind() error");
// listen()监听
if( listen(listenfd, 5) == -1)
err_sys("listen() error");
for(i=0; i<5; ++i)
{
// accept受理
connfd = accept(listenfd, (struct sockaddr*) NULL, NULL);
if( connfd == -1)
err_sys("accept() error");
else
printf("Connected client %d \n", i);
while((str_len = read(connfd, msgbuf, BUF_SIZE)) != 0)
write(connfd, msgbuf, str_len);
close(connfd); // 向连接的相应套接字发送EOF
}
close(listenfd); // 关闭服务器端套接字
return 0;
}
void err_sys(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void err_sys(char *message);
int main(int argc, char* argv[])
{
int sockfd;
char msgbuf[BUF_SIZE];
int str_len;
struct sockaddr_in serv_addr;
if(argc != 3)
{
printf("Usage: %s <IP> <port> \n", argv[0]);
exit(1);
}
// 调用socket()函数创建套接字文件描述符
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if( sockfd == -1)
err_sys("socket() error");
// 填充地址信息到结构中
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
// 调用connect()函数请求连接
if( connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
err_sys("connect() error");
else
puts("Connected...");
while(1)
{
fputs("Input message(Q to quit):", stdout);
fgets(msgbuf, BUF_SIZE, stdin);
if( !strcmp(msgbuf, "q\n") || !strcmp(msgbuf, "Q\n"))
break;
write(sockfd, msgbuf, strlen(msgbuf));
str_len = read(sockfd, msgbuf, BUF_SIZE-1);
msgbuf[str_len];
printf("Message from server: %s", msgbuf);
}
// 调用close()函数关闭连接
close(sockfd);
return 0;
}
void err_sys(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
运行结果:
3.3 以上代码存在的问题
由于TCP是不存在数据边界的,所以我们在这个回声客户端中所使用的读写方式可能会读到错误的结果。
我们将在下一章解决这个问题。