版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zxt_1/article/details/85305716
1.TCP通讯流程
- connect系统调用失败:
- connect连接的目标端口不存在或该端口仍被处于TIME_WAIT状态的连接所占用,服务器将给客户端发送一个RST复位报文段
- 目标端口存在,但connect在超时时间内未收到服务器但确认报文段
2.C/S模型
2.1普通版
服务器端:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERV_PORT 9527
int main(void)
{
int sfd, cfd;
int len, i;
char buf[BUFSIZ], clie_IP[BUFSIZ];
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
/*创建一个socket 指定IPv4协议族 TCP协议*/
sfd = socket(AF_INET, SOCK_STREAM, 0);
/*初始化一个地址结构 man 7 ip 查看对应信息*/
bzero(&serv_addr, sizeof(serv_addr)); //将整个结构体清零
serv_addr.sin_family = AF_INET; //选择协议族为IPv4
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址
serv_addr.sin_port = htons(SERV_PORT); //绑定端口号
/*绑定服务器地址结构*/
bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
/*设定链接上限,注意此处不阻塞*/
listen(sfd, 64); //同一时刻允许向服务器发起链接请求的数量
printf("wait for client connect ...\n");
/*获取客户端地址结构大小*/
clie_addr_len = sizeof(clie_addr_len);
/*参数1是sfd; 参2传出参数, 参3传入传入参数, 全部是client端的参数*/
cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /*监听客户端链接, 会阻塞*/
printf("client IP:%s\tport:%d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),
ntohs(clie_addr.sin_port));
while (1) {
/*读取客户端发送数据*/
len = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
/*处理客户端数据*/
for (i = 0; i < len; i++)
buf[i] = toupper(buf[i]);
/*处理完数据回写给客户端*/
write(cfd, buf, len);
}
/*关闭链接*/
close(sfd);
close(cfd);
return 0;
}
客户端:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERV_IP "127.0.0.1"
#define SERV_PORT 9527
int main(void)
{
int sfd, len;
struct sockaddr_in serv_addr;
char buf[BUFSIZ];
/*创建一个socket 指定IPv4 TCP*/
sfd = socket(AF_INET, SOCK_STREAM, 0);
/*初始化一个地址结构:*/
bzero(&serv_addr, sizeof(serv_addr)); //清零
serv_addr.sin_family = AF_INET; //IPv4协议族
inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); //指定IP 字符串类型转换为网络字节序 参3:传出参数
serv_addr.sin_port = htons(SERV_PORT); //指定端口 本地转网络字节序
/*根据地址结构链接指定服务器进程*/
connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
while (1) {
/*从标准输入获取数据*/
fgets(buf, sizeof(buf), stdin);
/*将数据写给服务器*/
write(sfd, buf, strlen(buf)); //写个服务器
/*从服务器读回转换后数据*/
len = read(sfd, buf, sizeof(buf));
/*写至标准输出*/
write(STDOUT_FILENO, buf, len);
}
/*关闭链接*/
close(sfd);
return 0;
}
2.2多进程服务器
服务器端:
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
void do_sigchild(int num)
{
while (waitpid(0, NULL, WNOHANG) > 0)
;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
pid_t pid;
struct sigaction newact;
newact.sa_handler = do_sigchild;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGCHLD, &newact, NULL);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
pid = fork();
if (pid == 0) {
Close(listenfd);
while (1) {
n = Read(connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(STDOUT_FILENO, buf, n);
Write(connfd, buf, n);
}
Close(connfd);
return 0;
} else if (pid > 0) {
Close(connfd);
} else
perr_exit("fork");
}
return 0;
}
2.3多线程服务器
服务器端:
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
struct s_info { //定义一个结构体, 将地址结构跟cfd捆绑
struct sockaddr_in cliaddr;
int connfd;
};
void *do_work(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看
while (1) {
n = Read(ts->connfd, buf, MAXLINE); //读客户端
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break; //跳出循环,关闭cfd
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); //打印客户端信息(IP/PORT)
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]); //小写-->大写
Write(STDOUT_FILENO, buf, n); //写出至屏幕
Write(ts->connfd, buf, n); //回写给客户端
}
Close(ts->connfd);
return (void *)0;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256]; //根据最大线程数创建结构体数组.
int i = 0;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket, 得到lfd
bzero(&servaddr, sizeof(servaddr)); //地址结构清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP
servaddr.sin_port = htons(SERV_PORT); //指定端口号 8000
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
Listen(listenfd, 128); //设置同一时刻链接服务器上限数
printf("Accepting client connect ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
/* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid); //子线程分离,防止僵线程产生.
i++;
}
return 0;
}