简单的C/S模型,实现小写转大写的功能
代码 客户端:
#include服务端:#include #include #include #include #include #include #include #define MAXLEN 64 #define PORT 2344 int main() { struct sockaddr_in serveraddr; int conncetfd; char buf[MAXLEN] = ""; conncetfd = socket(AF_INET, SOCK_STREAM, 0); //将结构体初始化,避免脏数据 bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; //协议族:IPV4协议 //htons(host_to_net_short)将16位的本地数据转换成16位网络数据(大小端转换) serveraddr.sin_port = htons(PORT); //端口 //将字符串转换成网络IPV4地址格式并存入serveraddr.sin_addr中 inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr); //发起连接,连接成功前阻塞 connect(conncetfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); //循环读取字符串,并交给服务器处理 while(~scanf("%s", buf)) { //遇到quit则退出循环 if(!strncmp("quit", buf, 4)) break; write(conncetfd, buf, strlen(buf)); int retlen = read(conncetfd, buf, MAXLEN); write(1, buf, retlen); } //关闭连接,给服务端发送quit让服务端关闭连接 write(conncetfd, "quit", strlen(buf)); close(conncetfd); return 0; }
#include#include #include #include #include #include #include #include #include #define PORT 2344 #define MAXLEN 64 void myperror(char* msg) { perror(msg); exit(1); } int main() { //服务端从listen之前的操作基本与客户端同理 struct sockaddr_in serveraddr, clientaddr; int listenfd, conncetfd, ret_status; char buf[MAXLEN]; socklen_t client_addrlen; listenfd = socket(AF_INET, SOCK_STREAM, 0); if(listenfd < 0) myperror("socket"); bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(PORT); serveraddr.sin_addr.s_addr = INADDR_ANY; ret_status = bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); if(ret_status < 0) myperror("bind"); //对 listenfd 进行监听 ret_status = listen(listenfd, 3); if(ret_status < 0) myperror("listen"); while(1) { //监听到新连接,调用accept完成握手,进行数据传输操作 client_addrlen = sizeof(clientaddr); conncetfd = accept(listenfd, (struct sockaddr *) &clientaddr, &client_addrlen); if(conncetfd < 0) myperror("accept"); char client_addrstr[17]; int retlen; //后面都是数据传输操作,IO方面知识 while((retlen = read(conncetfd, buf, MAXLEN)) > 0) { //收到客户端发送的quit信号,关闭连接,结束程序 if(!strncmp("quit", buf, 4)) break; printf("received from %s:%dn", inet_ntop(AF_INET, &clientaddr.sin_addr, client_addrstr, 16), ntohs(clientaddr.sin_port)); for(int i = 0; i < retlen; i++) buf[i] = toupper(buf[i]); ret_status = write(conncetfd, buf, retlen); if(ret_status < 0) myperror("write"); } if(retlen < 0) myperror("read"); close(conncetfd); printf("client close from %s:%dn", inet_ntop(AF_INET, &clientaddr.sin_addr, client_addrstr, 16), ntohs(clientaddr.sin_port)); } return 0; }
运行截图
服务端:
客户端:
重难点及相关知识 1.客户端服务端连接过程
-
服务端和客户端初始化 socket ,得到⽂件描述符;
-
服务端调⽤ bind ,将绑定在 IP 地址和端⼝;
-
服务端调⽤ listen ,进⾏监听;
-
服务端调⽤ accept ,等待客户端连接;
-
客户端调⽤ connect ,向服务器端的地址和端⼝发起连接请求;
-
服务端 accept 返回⽤于传输的 socket 的⽂件描述符;
-
客户端调⽤ write 写⼊数据;服务端调⽤ read 读取数据;
-
客户端断开连接时,会调⽤ close ,那么服务端 read 读取数据的时候,就会读取到了 EOF ,待处理完数据后,服务端调⽤ close ,表示连接关闭。这里我们特殊处理读到quit就可以关闭连接了。
这⾥需要注意的是,服务端调⽤ accept 时,连接成功了会返回⼀个已完成连接的 socket,后续⽤来传输数据。
2. struct sockaddr 、struct sockaddr_in 、struct sockaddr_unstruct sockaddr 是通用的套接字地址,使用系统调用如bind的时候其中一个参数的参数类型就是这个。
struct sockaddr
{
unsigned short sa_family;
char sa_data[14];
};
而 struct sockaddr_in 则是 internet 环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向 sockaddr_in 结构的指针也可以指向 sockaddr 。一般情况下,需要把 sockaddr_in 结构强制转换成 sockaddr 结构再传入系统调用函数中,比如bind。
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
struct sockaddr_un 用于本地 socket 结构,结构如下,用法和struct sockaddr_in 类似。
struct sockaddr_un
{
sa_family_t sun_family;
char sun_path[UNIX_PATH_MAX];
};
如图所示:
htonl() – “Host to Network Long” – “对32位数据进行本地到网络的字节序转换”
ntohl() – “Network to Host Long” – “对32位数据进行网络到本地的字节序转换”
htons() – “Host to Network Short” – “对16位数据进行本地到网络的字节序转换”
ntohs() – “Network to Host Short” – “对16位数据进行网络到本地的字节序转换”
因为Linux大多是小端,而网络上的机器都是大端,在网络上发送数据需要进行字节序转换,否则可能会发生通信异常。
4.inet_ntop()、inet_pton()、inet_addr()(1)int inet_pton(int family, const char *strptr, void *addrptr);
将点分十进制的ip地址转化为用于网络传输的数值格式
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
(2)const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL
这两个函数是随IPv6出现的函数,对于IPv4地址和IPv6地址都适用,函数中p和n分别代表表达(presentation)和数值(numeric)。地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构的二进制值。
(3) in_addr_t inet_addr(const char *cp);
inet_addr 函数转换网络主机地址(如192.168.1.10)为网络字节序二进制值,如果参数char *cp无效,函数返回-1( INADDR_NONE ),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理
参考资料
-
小林coding —— 图解网络
-
inet_pton()和inet_ntop()函数详解
-
struct sockaddr struct sockaddr_in struct sockaddr_un 结构详解



