基于socket套接字基本函数,我们就可以实现一些简单的回显服务器,及基于UDP/TCP的服务器和客户端。
UDP回显服务器:
简单思路:
1.socket()创建socket套接口
2.bind() 绑定IP地址端口号
3.循环的从连接端口接收数据,并重新写入
//
// 服务器:
// 1.启动
// 2.进入死循环(事件循环)
// a>从socket中读取请求(Request)
// b>根据Request的内容计算生成Response
// c>把Response响应写回到socket
//
//此处为实现方便,实现一个echo_server(回显服务器)
/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//绑定的IP地址和端口号 ./server 127.0.0.1 9090
int main(int argc, char* argv[]) {
//0.校验命令行参数
if(argc != 3)
{
printf("usage : ./server [ip] [port]\n");
return 1;
}
//1.服务器的初始化
//a>创建socket
//AF_INET :ipv4协议 (udp) 面向数据报
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0)
{
perror("socket");
return 1;
}
//b>绑定ip地址端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET; //协议族
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
//点分十进制的字符串ip地址转换成了数字(网络字节序)
int ret = bind(fd, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
close(fd);
return 1;
}
printf("Server start...\n");
//2.进入死循环
while(1)
{
// a>从socket中读取请求(Request)
char buf[1024] ={0};
sockaddr_in peer; //对端的Ip地址端口号
socklen_t len = sizeof(peer);
ssize_t read_size = recvfrom(fd,buf, sizeof(buf) - 1, 0, (sockaddr*)&peer, &len);
if(read_size < 0)
{
perror("recvfrom");
continue; //忽略当前的错误,防止服务器崩溃
}
buf[read_size] ='\0';
// b>根据Request的内容计算生成Response(因为是回显服务器,此步骤省略)
// inet_ntoa 将数字地址 转成点分十进制的字符串
// ntohs 网络字节序转主机字节序
printf("[clien%s:%d] say: %s\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf);
// c>把Response响应写回到socket
sendto(fd, buf, strlen(buf), 0, (sockaddr*)&peer, sizeof(peer));//strlen防止把1024全部传输,可能会造成无效数据的传递,效率低
}
close(fd); //关闭文件描述符
return 0;
}
UDP客户端:
简单思路:
1.socket()创建套接口
2.初始化服务器端口
3.循环的从标准输入读入数据写入服务器端口,然后尝试从服务器端口读
//
// 客户端:
// 1.用户输入数据;从标准输入输入字符串
// 2.把这个字符串发送给服务器 (请求 Request)
// 3.从服务器读取返回结果 (响应Response)
// 4.将返回结果打印到标准输出
/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
// ./client [server_ip] [server_port]
int main(int argc, char* argv[]) {
if(argc != 3)
{
printf("Usage ./client [ip], [port]\n");
return 1;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0)
{
perror("socket");
return 1;
}
sockaddr_in server_addr; //服务器端的ip地址和端口号
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
while(1)
{
//1.读取数据
char buf[1024]= {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if(read_size < 0)
{
perror("read");
return 1; //客户端根据需要选择出错程序终止或者忽略出错
}
if(read_size == 0)
{
printf("read finish\n");
return 0;
}
//2.发送数据到服务器
sendto(fd, buf, strlen(buf), 0, (sockaddr*)&server_addr, sizeof(server_addr));
//3.尝试从服务器读取响应
//recvfrom 第五个第六个参数表示对端的Ip地址端口号,
//此时由于客户端收到的数据一定是服务器端返回的相应数据
//所以此时就可以忽略掉对端的ip地址和端口号(NULL)
char buf_response[1024] = {0};
read_size = recvfrom(fd, buf_response, sizeof(buf_response) - 1,
0, NULL, NULL);
if(read_size < 0)
{
perror("recvfrom");
return 1;
}
buf_response[read_size] = '\0';
//4.把响应写到标准输出
printf("server response: %s\n", buf_response);
}
close(fd);
return 0;
}
TCP服务器:
多进程版:
///
// 服务器基本流程:
// 1.从socket中读取数据(request)
// 2.根据Request 计算生成Response
// 3.把Response写回客户端
// 由于是回显服务器、计算生成response步骤省略
/
//多进程版本
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//当前需要改进为多进程版本
//每个链接需要创建一个子进程来处理请求
void ProcessGrandChild(int new_sock, sockaddr_in* client_addr)
{
//a>从客户端读取数据
while(1)
{
char buf[1024] = {0};
ssize_t read_size = read(new_sock, buf, sizeof(buf) - 1);
if(read_size < 0)
{
perror("read");
continue;
}
if(read_size == 0)
{
//TCP中,如果read的返回值为0, 说明对端关闭了连接
printf("[client %s] disconnect!\n", inet_ntoa(client_addr->sin_addr));
close(new_sock);
return;
}
buf[read_size] = '\0';
//b> 根据请计算响应(省略)
printf("[client %s] %s\n", inet_ntoa(client_addr->sin_addr), buf);
//把相应结果写回到客户端
write(new_sock, buf, strlen(buf));
}
return;
}
void ProcessConnect(int new_sock, sockaddr_in* client_addr) {
//创建子进程
pid_t pid =fork();
if(pid < 0)
{
perror("fork");
return;
}
else if(pid == 0) //子进程
{
if(fork() == 0) //创建孙子进程来处理,孙子进程返回由一号进程负责回收
{
ProcessGrandChild(new_sock, client_addr);
}
exit(0);
}
else
{
close(new_sock);
waitpid(pid, NULL, 0);
}
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage : ./tcp_server [ip] [prot]\n");
return 1;
}
//1.创建socket
// 面向字节流--tcp
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0)
{
perror("socket");
return 1;
}
//2.绑定端口号
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int ret = bind(listen_sock, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
close(listen_sock);
return 1;
}
//3. 使用listen允许服务器被客户端链接 --- 转换成被动模式
ret = listen(listen_sock, 5);
if(ret < 0)
{
perror("listen");
close(listen_sock);
return 1;
}
//4. 服务器初始化完成,进入时间循环
// TCP的链接和管理是有内核管理的
printf("Tcp_server Init Ok\n");
while(1)
{
//把内核中建立好的链接放到用户代码空间中处理
sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (sockaddr*)&peer, &len); //获取客户端
//链接后的到的新的socket
if(new_sock < 0)
{
perror("accept");
continue;
}
printf("[client %s] connect\n", inet_ntoa(peer.sin_addr));
ProcessConnect(new_sock, &peer); //处理链接
}
return 0;
}
多线程版:
///
// 服务器基本流程:
// 1.从socket中读取数据(request)
// 2.根据Request 计算生成Response
// 3.把Response写回客户端
// 由于是回显服务器、计算生成response步骤省略
/
//多线程版本
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//当前需要改进为多线程版本
typedef struct Arg
{
int fd;
sockaddr_in addr;
}Arg;
void ProcessRequest(int client_fd, sockaddr_in* client_addr)
{
char buf[1024] = {0};
while(1)
{
ssize_t read_size = read(client_fd, buf, sizeof(buf));
if(read_size < 0)
{
perror("read");
continue;
}
if(read_size == 0)
{
//对端关闭
printf("Client %s disconnected..\n", inet_ntoa(client_addr->sin_addr));
close(client_fd);
break;
}
buf[read_size] = '\0';
printf("Client %s say: %s\n", inet_ntoa(client_addr->sin_addr), buf);
write(client_fd,buf, strlen(buf));
}
return;
}
void* EntryWork(void* arg)
{
Arg* ptr = (Arg*)arg;
ProcessRequest(ptr->fd, &ptr->addr);
free(ptr);
return NULL;
}
// ./server 127.0.0.1 9090
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage: ./tcp_server [ip] [port]");
}
//创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return 1;
}
//绑定
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));;
int ret = bind(sock, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
close(sock);
return 1;
}
//监听
ret = listen(sock, 5);
if(ret < 0)
{
perror("listen");
close(sock);
return 1;
}
printf("tcp_server start...\n");
while(1)
{
sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client_fd = accept(sock, (sockaddr*)&client_addr, &len);
if(client_fd < 0)
{
perror("accept");
continue;
}
pthread_t tid;
Arg* arg = (Arg*)malloc(sizeof(Arg));
arg->fd = client_fd;
arg->addr = client_addr;
pthread_create(&tid, NULL, EntryWork, (void*)arg);
pthread_detach(tid);
}
return 0;
}
TCP回显客户端:
///
// 服务器基本流程:
// 1.从标准输入读入字符串
// 2.把读入的字符串发送给服务器
// 3.尝试从服务器读取响应数据
// 4.把响应数据打印到标准输出
/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int main(int argc, char* argv[])
{
if(argc != 3)
{
printf("Usage : ./client [ip] [port]\n");
return 1;
}
//1.创建socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
return 1;
}
//2.建立连接
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
int ret = connect(fd, (sockaddr*)&server_addr, sizeof(server_addr));
if(ret < 0)
{
perror("connect");
close(fd);
return 1;
}
//3.进入循环
printf("connect success....\n");
while(1)
{
// a>从标准输入读取数据
printf("[Clent:#]");
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if(read_size < 0)
{
perror("read");
return 1;
}
if(read_size == 0)
{
printf("read done\n");
return 0;
}
buf[read_size] = '\0';
// b>把读入的数据发送到服务器上
write(fd, buf, strlen(buf)); //这里也需要判定,但是,一般不会出错,本代码功能简单故而选择忽略
// c>从服务器读取响应结果
char buf_resp[1024] = {0};
read_size = read(fd, buf_resp, sizeof(buf_resp) - 1);
if(read_size < 0)
{
perror("read");
close(fd);
return 1;
}
if(read_size == 0) //服务器断开链接
{
printf("server close socket\n");
return 0;
}
buf_resp[read_size] = '\0';
// d>把结果打印到标准输出
printf("server response: %s\n", buf_resp);
}
close(fd);
return 0;
}