同一时间只能响应一个客户端的请求,将小写字母转换成大写字母
服务器端代码如下:
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#define SERVPORT 8080
int main()
{
// 地址结构
/*
struct sockaddr //通用地址 ---- 这个结构体里没有端口号
{
u_short sa_family; ---- Sa_family 地址族,采用“AF_xxx”的形式,如:AF_INET
char sa_data[14]; ---- Sa_data 14字节的特定协议地址
---- "192.168.1.10:55555" --- 要做字符串解析
};
*/
// u_short ---- 无符号短整数
/*
struct sockaddr_in
{
short sin_family; ---- Internet 地址族
unsigned short int sin_port; ---- 端口号
struct in_addr sin_addr; ---- ip地址
unsigned char sin_zero[8]; ---- 填0
};
struct in_addr
{
unsigned long s_addr;
};
*/ // ---- S_addr 32位的地址
struct sockaddr_in servaddr, cliaddr;
int listenfd, connfd;
socklen_t clilen;
char mesg[1024];
int bytes_recv;
int i;
listenfd = socket(AF_INET, SOCK_STREAM, 0); // socket(套接字)接口实现,socket 一种文件描述符
// SOCK_STREAM 流式套接字 ---- 可靠的面向连接的,TCP协议
// SOCK_DGRAM 数据报套接字 ---- 一种无连接的,数据通过相互独立的报文进行传输,UDP协议
// 原始套接字(很少用) ---- 用在测试新的网络协议
/*
int socket(int family,int type,int protocol)
socket() 打开一个网络通讯端口,成功返回文件描述符,出错返回-1
ipv4 ---- family ---- AF_INET
TCP ---- tpye ---- SOCK_STREAM UDP ---- tpye ---- SOCK_DGRAM
protocol (协议) ---- 指定为 0
*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 32字节
//servaddr.sin_addr.s_addr = inet_addr("192.168.1.105"); //inet_addr ---- 不用字节序转换
/*
int inet_aton(const char *cp,struct in_addr* inp);
char *inet_ntoa(struct in_addr in);
*/
// INADDR_ANY ---- 表示本机的任意ip地址
// (服务器可能存在多个网卡,每个网卡可能绑定多个ip地址)
// (这样设置可以在所有的ip地址上监听,直到某个客户端建立了连接时才确定到底用哪个ip地址)
// 字节序转换 #include<arpa/inet.h>
/*
uint32_t htonl(uint32_t hostlong); 把 unsigned long 类型从主机序转换到网络序
uint16_t htons(uint16_t hostlong); 把 unsigned short 类型从主机序转换到网络序
uint32_t ntohl(uint32_t netlong); 把 unsigned long 类型从网络序转换到主机序
uint32_t ntohl(uint16_t netshort); 把 unsigned short 类型从网络序转换到主机序
h --- host (主机)
n --- network
l --- 32位长整数
s --- 16位短整数
如果主机是小端字节序,函数将参数做相应的大小端转换后返回
如果主机是大端字节序,函数不做转换,参数原封不动的返回
*/
servaddr.sin_port = htons(SERVPORT); // SERVPORT ---- 端口号
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
/*
int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen);
bind() 将参数 socket 和 myaddr 绑定在一起,
使sockfd 监听 myaddr 所描述的地址和端口号
*/
listen(listenfd, 1024); // linux:#include<sys/listen.h>
/*
等待别人连接
listen在套接字函数中表示让一个套接字处于监听到来的连接请求的状态
int listen(int fd, int backlog);
sockfd 一个已绑定未被连接的套接字描述符
backlog 连接请求队列的最大长度
listen函数使用主动连接套接字变为被连接套接口
使得一个进程可以接受其它进程的请求,从而成为一个服务器进程
在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数一般在调用bind之后-调用accept之前调用。
*/
for (;;) //等价与 while(1)
{
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
/*
accept 连接 ---- 实现三次握手
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen)
返回值:连接成功,返回文件描述符 连接文件描述符
实现收发就用accept的返回值
三方握手完成后,服务器调用accept()接受连接
如果服务器调用accept() 还没有客户端的连接请求,就会阻塞等待客户机连接
cliaddr 传出参数 返回时,传出客户端的地址和端口号
addrlen 传入传出参数,传入是调用 所提供的缓冲区cliaddr的长度以避免缓冲区溢出问题
传出是客户端地址结构体的实际长度(可能没有占满调用者提供的缓冲区)
给cliaddr参数传NULL,表示不关心客户端的地址
*/
while (1)
{
bytes_recv = recvfrom(connfd, mesg, 1024, 0, (struct sockaddr *)&cliaddr, &clilen);
/*
recvfrom函数(经sockfd接收数据)
函数原型:
ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socklen_t *fromlen);
ssize_t ---- long int
socklen_t ---- int
sockfd:标识一个已连接套接口的描述字。
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。是以下一个或者多个标志的组合体,可通过“ | ”操作符连在一起,MSG_DONTWAIT:操作不会被阻塞。
MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。
MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
MSG_EOR:指示记录的结束,返回的数据完成一个记录。
MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。
from:(可选)指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向from缓冲区长度值。
如果正确接收返回接收到的字节数,失败返回-1.
*/
if (0 == bytes_recv)
{
printf("client is offline!\n");
break;
}
mesg[bytes_recv] = '\0';
printf("revc ok\n");
for (i = 0; i < bytes_recv; i++)
{
mesg[i] = toupper(mesg[i]);
/* //#include <ctype.h>
int toupper(int c);
将字符c转换为大写英文字母
如果 c 有相对应的大写字母,则该函数返回 c 的大写字母,
否则 c 保持不变。返回值是一个可被隐式转换为 char 类型的 int 值
*/
}
sendto(connfd, mesg, bytes_recv, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
/*
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
只有short int flout > 1个字节的数据,需要字节序转换
*/
}
close(connfd); //shutdown --- 可以根据要求断开连接
}
close(listenfd);
return 0;
}
客户端代码:
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#define SERVPORT 8080
int main(int argc, char const *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
char send_line[1024];
char recv_line[1024];
int bytes_recv;
if (argc != 2)
{
printf("need server IP!\n");
exit(0);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVPORT);
servaddr.sin_addr.s_addr = inet_addr(argv[1]); //"inet_aton ---- 需要字节序转换"
//inet_addr ---- 不用字节序转换
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
/*
int connect(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);
客户机需要调用connect()连接服务器
connect() 与 bind() 的参数形式一致
bind() ---- 自己的地址
connect() ---- 对方的地址
返回值:成功 0 错误 -1
*/
while (fgets(send_line, 1024, stdin) != NULL)
//stdin是标准输入流,可以理解为键盘
//stdout是标准输出流,可以理解是屏幕
/*
char *fgets(char *str, int n, FILE *stream);
str ---- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
n ---- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
stream ---- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
如果成功,该函数返回相同的 str 参数。
如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
如果发生错误,返回一个空指针。
在读字符时遇到end-of-file,则eof指示器被设置,
如果还没读入任何字符就遇到这种情况,则stream保持原来的内容,返回NULL;
如果发生读入错误,error指示器被设置,返回NULL,stream的值可能被改变。
*/
{
sendto(sockfd, send_line, strlen(send_line), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
bytes_recv = recvfrom(sockfd, recv_line, 100, 0, NULL, NULL);
recv_line[bytes_recv] = '\0';
printf("result is : %s\n", recv_line);
}
close(sockfd);
return 0;
}
运行结果如下
在中间客户端发送数据时候会发生阻塞,无法响应其他客户端
中间客户端下线之后,右端收到消息
全部下线之后: