C语言之网络编程(服务器和客户端)
Linux网络编程 1、 套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。 常用的TCP/IP协议的3种套接字类型如下所示。 (1)流套接字(SOCK_STREAM): 流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission ControlProtocol)协议。 (2) 数据报套接字(SOCK_DGRAM): 数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。 (3) 原始套接字(SOCK_RAW):(一般不用这个套接字) 原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW 2、 套接字基本函数: (1) 创建套接字:int socket(int family, int type, intprotocol); 功能介绍: 在Linux操作系统中,一切皆文件,这个大家都知道,个人理解创建socket的过程其实就是一个获得文件描述符的过程,当然这个过程会是比较复杂的。可以从内核中找到创建socket的代码,并且socket的创建和其他的listen,bind等操作分离开来。socket函数完成正确的操作是返回值大于0的文件描述符,当返回小于0的值时,操作错误。同样是返回一个文件描述符,但是会因为三个参数组合不同,对于数据具体的工作流程不同,对于应用层编程来说,这些也是不可见的。 参数说明: 从socket创建的函数可以看出,socket有三个参数,family代表一个协议族,比较熟知的就是AF_INET,PF_PACKET等;第二个参数是协议类型,常见类型是SOCK_STREAM,SOCK_DGRAM, SOCK_RAW, SOCK_PACKET等;第三个参数是具体的协议,对于标准套接字来说,其值是0,对于原始套接字来说就是具体的协议值。 (2) 套接字绑定函数: intbind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); 功能介绍: bind函数主要应用于服务器模式一端,其主要的功能是将addrlen长度 structsockaddr类型的myaddr地址与sockfd文件描述符绑定到一起,在sockaddr中主要包含服务器端的协议族类型,网络地址和端口号等。在客户端模式中不需要使用bind函数。当bind函数返回0时,为正确绑定,返回-1,则为绑定失败。 参数说明: bind函数的第一个参数sockfd是在创建socket套接字时返回的文件描述符。 bind函数的第二个参数是structsockaddr类型的数据结构,由于structsockaddr数据结构类型不方便设置,所以通常会通过对tructsockaddr_in进行地质结构设置,然后进行强制类型转换成structsockaddr类型的数据, (3) 监听函数:int listen(int sockfd, int backlog); 功能介绍: 刚开始理解listen函数会有一个误区,就是认为其操作是在等在一个新的connect的到来,其实不是这样的,真正等待connect的是accept操作,listen的操作就是当有较多的client发起connect时,server端不能及时的处理已经建立的连接,这时就会将connect连接放在等待队列中缓存起来。这个等待队列的长度有listen中的backlog参数来设定。listen和accept函数是服务器模式特有的函数,客户端不需要这个函数。当listen运行成功时,返回0;运行失败时,返回值位-1. 参数说明: sockfd是前面socket创建的文件描述符;backlog是指server端可以缓存连接的最大个数,也就是等待队列的长度。 (4) 请求接收函数: int accept(int sockfd, structsockaddr *client_addr, socklen_t *len); 功能介绍: 接受函数accept其实并不是真正的接受,而是客户端向服务器端监听端口发起的连接。对于TCP来说,accept从阻塞状态返回的时候,已经完成了三次握手的操作。Accept其实是取了一个已经处于connected状态的连接,然后把对方的协议族,网络地址以及端口都存在了client_addr中,返回一个用于操作的新的文件描述符,该文件描述符表示客户端与服务器端的连接,通过对该文件描述符操作,可以向client端发送和接收数据。同时之前socket创建的sockfd,则继续监听有没有新的连接到达本地端口。返回大于0的文件描述符则表示accept成功,否则失败。 参数说明: sockfd是socket创建的文件描述符;client_addr是本地服务器端的一个structsockaddr类型的变量,用于存放新连接的协议族,网络地址以及端口号等;第三个参数len是第二个参数所指内容的长度,对于TCP来说其值可以用sizeof(structsockaddr_in)来计算大小,说要说明的是accept的第三个参数要是指针的形式,因为这个值是要传给协议栈使用的。 (5)客户端请求连接函数: intconnect(int sock_fd, struct sockaddr *serv_addr,int addrlen); 功能介绍: 连接函数connect是属于client端的操作函数,其目的是向服务器端发送连接请求,这也是从客户端发起TCP三次握手请求的开始,服务器端的协议族,网络地址以及端口都会填充到connect函数的serv_addr地址当中。当connect返回0时说明已经connect成功,返回值是-1时,表示connect失败。 参数说明: connect的第一个参数是socket创建的文件描述符;第二个参数是一个structsockaddr类型的指针,这个参数中设置的是要连接的目标服务器的协议族,网络地址以及端口号;第三个参数表示第二个参数内容的大小,与accept不同,这个值不是一个指针。 在服务器端和客户端建立连接之后是进行数据间的发送和接收,主要使用的接收函数是recv和read,发送函数是send和write。因为对于socket套接字来说,最终实际操作的是文件描述符,所以可以使用对文件进行操作的接收和发送函数对socket套接字进行操作。read和write函数是文件编程里的知识,所以这里不再做多与的赘述。 3、 有了以上的知识,那么我们就可以编写一个简单的服务器和客户端了 (1) 简易服务器:这个服务器只能与一个客户端相连接,如果有多个客户端就不能用这个服务器进行连接。 代码: #include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h> #define PORT 9990 //端口号
#define SIZE 1024 //定义的数组大小 int Creat_socket() //创建套接字和初始化以及监听函数
{
int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个负责监听的套接字
if(listen_socket == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 端口号 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //连接
if(ret == -1)
{
perror("bind");
return -1;
} ret = listen(listen_socket, 5); //监听
if(ret == -1)
{
perror("listen");
return -1;
}
return listen_socket;
} int wait_client(int listen_socket)
{
struct sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
printf("等待客户端连接。。。。\n");
int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //创建一个和客户端交流的套接字
if(client_socket == -1)
{
perror("accept");
return -1;
} printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr)); return client_socket;
} void hanld_client(int listen_socket, int client_socket) //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
{
char buf[SIZE];
while(1)
{
int ret = read(client_socket, buf, SIZE-1);
if(ret == -1)
{
perror("read");
break;
}
if(ret == 0)
{
break;
}
buf[ret] = '\0';
int i;
for(i = 0; i < ret; i++)
{
buf[i] = buf[i] + 'A' - 'a';
} printf("%s\n", buf);
write(client_socket, buf, ret); if(strncmp(buf, "end", 3) == 0)
{
break;
}
}
close(client_socket);
} int main()
{
int listen_socket = Creat_socket(); int client_socket = wait_client(listen_socket); hanld_client(listen_socket, client_socket); close(listen_socket); return 0;
}
(2) 多进程并发服务器:该服务器就完全弥补了上一个服务器的不足,可以同时处理多个客户端,只要有客户端来连接它,他就能响应。在我们这个服务器中,父进程主要负责监听,所以在父进程一开始就要把父进程的接收函数关闭掉,防止父进程在接收函数处阻塞,导致子进程不能创建成功。同理,子进程主要负责接收客户端,并做相关处理,所以子进程在一创建就要把监听函数关闭,不然会导致服务器功能的紊乱。这个服务器有一个特别要注意的是,子进程在退出时会产生僵尸进程,所以我们一定要对子进程退出后进行处理。 代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h> #define PORT 9990
#define SIZE 1024 int Creat_socket() //创建套接字和初始化以及监听函数
{
int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个负责监听的套接字
if(listen_socket == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 端口号 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //连接
if(ret == -1)
{
perror("bind");
return -1;
} ret = listen(listen_socket, 5); //监听
if(ret == -1)
{
perror("listen");
return -1;
}
return listen_socket;
} int wait_client(int listen_socket)
{
struct sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
printf("等待客户端连接。。。。\n");
int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //创建一个和客户端交流的套接字
if(client_socket == -1)
{
perror("accept");
return -1;
} printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr)); return client_socket;
} void hanld_client(int listen_socket, int client_socket) //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
{
char buf[SIZE];
while(1)
{
int ret = read(client_socket, buf, SIZE-1);
if(ret == -1)
{
perror("read");
break;
}
if(ret == 0)
{
break;
}
buf[ret] = '\0';
int i;
for(i = 0; i < ret; i++)
{
buf[i] = buf[i] + 'A' - 'a';
} printf("%s\n", buf);
write(client_socket, buf, ret); if(strncmp(buf, "end", 3) == 0)
{
break;
}
}
close(client_socket);
} void handler(int sig)
{ while (waitpid(-1, NULL, WNOHANG) > 0)
{
printf ("成功处理一个子进程的退出\n");
}
} int main()
{
int listen_socket = Creat_socket(); signal(SIGCHLD, handler); //处理子进程,防止僵尸进程的产生
while(1)
{
int client_socket = wait_client(listen_socket); //多进程服务器,可以创建子进程来处理,父进程负责监听。
int pid = fork();
if(pid == -1)
{
perror("fork");
break;
}
if(pid > 0)
{
close(client_socket);
continue;
}
if(pid == 0)
{
close(listen_socket);
hanld_client(listen_socket, client_socket);
break;
}
} close(listen_socket); return 0;
}
(3) 多线程并发服务器:上一个多进程服务器有一个缺点,就是每当一个子进程得到响应的时候,都要复制父进程的一切信息,这样就导致了CPU资源的浪费,当客户端有很多来连接这个服务器的时候,就会产生很多的子进程,会导致服务器的响应变得很慢。所以我们就想到了多线程并发服务器,我们知道线程的速度是进程的30倍左右,所以我们就用线程来做服务器。 代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h> #define PORT 9990
#define SIZE 1024 int Creat_socket() //创建套接字和初始化以及监听函数
{
int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个负责监听的套接字
if(listen_socket == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 端口号 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //连接
if(ret == -1)
{
perror("bind");
return -1;
} ret = listen(listen_socket, 5); //监听
if(ret == -1)
{
perror("listen");
return -1;
}
return listen_socket;
} int wait_client(int listen_socket)
{
struct sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
printf("等待客户端连接。。。。\n");
int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //创建一个和客户端交流的套接字
if(client_socket == -)
{
perror("accept");
return -;
} printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr)); return client_socket;
} void hanld_client(int listen_socket, int client_socket) //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
{
char buf[SIZE];
while()
{
int ret = read(client_socket, buf, SIZE-);
if(ret == -)
{
perror("read");
break;
}
if(ret == )
{
break;
}
buf[ret] = '\0';
int i;
for(i = 0; i < ret; i++)
{
buf[i] = buf[i] + 'A' - 'a';
} printf("%s\n", buf);
write(client_socket, buf, ret); if(strncmp(buf, "end", 3) == )
{
break;
}
}
close(client_socket);
} int main()
{
int listen_socket = Creat_socket(); while()
{
int client_socket = wait_client(listen_socket); pthread_t id;
pthread_create(&id, NULL, hanld_client, (void *)client_socket); //创建一个线程,来处理客户端。 pthread_detach(id); //把线程分离出去。
} close(listen_socket); return ;
}
()客户端:客户端相对于服务器来说就简单多了,客户端只需要创建和服务器相连接的套接字,然后对其初始化,然后再进行连接就可以了,连接上服务器就可以发送你想发送的数据了。 代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h> #define PORT 9990
#define SIZE 1024 int main()
{
int client_socket = socket(AF_INET, SOCK_STREAM, 0); //创建和服务器连接套接字
if(client_socket == -)
{
perror("socket");
return -;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 端口号 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */
inet_aton("127.0.0.1", &(addr.sin_addr)); int addrlen = sizeof(addr);
int listen_socket = connect(client_socket, (struct sockaddr *)&addr, addrlen); //连接服务器
if(listen_socket == -)
{
perror("connect");
return -;
} printf("成功连接到一个服务器\n"); char buf[SIZE] = {}; while(1) //向服务器发送数据,并接收服务器转换后的大写字母
{
printf("请输入你相输入的:");
scanf("%s", buf);
write(client_socket, buf, strlen(buf)); int ret = read(client_socket, buf, strlen(buf)); printf("buf = %s", buf);
printf("\n");
if(strncmp(buf, "END", 3) == 0) //当输入END时客户端退出
{
break;
}
}
close(listen_socket); return ;
}
C语言之网络编程(服务器和客户端)的更多相关文章
- 循序渐进Socket网络编程(多客户端、信息共享、文件传输)
循序渐进Socket网络编程(多客户端.信息共享.文件传输) 前言:在最近一个即将结束的项目中使用到了Socket编程,用于调用另一系统进行处理并返回数据.故把Socket的基础知识总结梳理一遍. 1 ...
- 5.3linux下C语言socket网络编程简例
原创文章,转载请注明转载字样和出处,谢谢! 这里给出在Linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到客户端的连接后,发送数据给客户端:客户端在接受到数据后 ...
- c/c++ 网络编程 单纯http客户端,服务器端
网络编程 单纯http客户端,服务器端 1,http客户端 2,http服务器端 http客户端: #include <stdio.h> #include <sys/types.h& ...
- 网络编程—【自己动手】用C语言写一个基于服务器和客户端(TCP)!
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西--socket(套接字). socket(套接字):简单来讲,socket就是用于描述IP地 ...
- 《UNIX网络编程》TCP客户端服务器例子
最近在看<UNIX网络编程>(简称unp)和<Linux程序设计>,对于unp中第一个获取服务器时间的例子,实践起来总是有点头痛的,因为作者将声明全部包含在了unp.h里,导致 ...
- 循序渐进Java Socket网络编程(多客户端、信息共享、文件传输)
目录[-] 一.TCP/IP协议 二.TCP与UDP 三.Socket是什么 四.Java中的Socket 五.基本的Client/Server程序 六.多客户端连接服务器 七.信息共享 八.文件传输 ...
- linux下C语言socket网络编程简例
原创文章,转载请注明转载字样和出处,谢谢! 这里给出在linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到client的连接后,发送数据给client:clie ...
- 【python】网络编程-SocketServer 实现客户端与服务器间非阻塞通信
利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信.首先,先了解下SocketServer模块中可供使用的类:BaseServer:包含服务器的核心功能与混合(mix-in)类 ...
- 《UNIX网络编程》TCP客户端服务器:并发、消息回显
经过小小改动,把前面基础的例子做出一点修改. 并发服务器,服务器每accept一个请求就fork()一个新的子进程. 编译运行方法同前一篇. /*client_tcp.c*/ #include < ...
随机推荐
- 【题解】 Luogu CF375D Tree and Queries
原题传送门 这道题要用树链剖分,我博客里有对树链剖分的详细介绍 我博客中对莫队的详细介绍 莫队好题 我一上来想写线段树,随后觉得不好写并弃坑 我们可以看见没有修改操作,钦定莫队 但这是在树上,所以不能 ...
- Java基础语法(上)
Java编译报错出现非法字符,原因是存在中文字符. Java关键字的字母都是小写. Java是一种强类型语言,针对每一种数据都给出了明确的数据类型. 数据类型分类: A:基本数据类型 B:引用数据类型 ...
- topcoder srm 390 div1
problem1 link 记录一个模$k$之后的值是否出现过,出现过则出现循环,无解:否则最多$k$ 次一定能出现0. import java.util.*; import java.math.*; ...
- ODAC(V9.5.15) 学习笔记(四)TMemDataSet (3)
3.其他 名称 类型 说明 GetBlob TBlob 按照字段名获取当前数据集中某个Blob类型的字段值,并以TBlob对象形式返回 Prepared Boolean 检查Query的SQL是否已准 ...
- DataSnap服务器从xe2升级到xe5报错的处理
DataSnap服务器从xe2升级到xe5环境下,能够正常编译,但运行后会报错,如下图: 处理参考: http://blogs.embarcadero.com/pawelglowacki/2013/0 ...
- CentOS 使用 Docker 安装 Sentry
官网介绍:Sentry是一个实时事件日志记录和汇集的日志平台,其专注于错误监控,以及提取一切事后处理所需的信息.他基于Django开发,目的在于帮助开发人员从散落在多个不同服务器上的日志文件里提取发掘 ...
- P2234 [HNOI2002]营业额统计(Splay树)题解
思路:Splay数查找前驱后继 代码: #include<iostream> #include<cstdio> #include<cstring> #include ...
- SPOJ 694 DISUBSTR - Distinct Substrings
思路 求本质不同的子串个数,总共重叠的子串个数就是height数组的和 总子串个数-height数组的和即可 代码 #include <cstdio> #include <algor ...
- (转载)WinformGDI+入门级实例——扫雷游戏(附源码)
本文将作为一个入门级的.结合源码的文章,旨在为刚刚接触GDI+编程或对相关知识感兴趣的读者做一个入门讲解.游戏尚且未完善,但基本功能都有,完整源码在文章结尾的附件中. 整体思路: 扫雷的游戏界面让我从 ...
- 日系插画学习笔记(一):SAI软件基础
检测驱动是否安装正确:1.画笔没有压感,线条没有粗细变化2.画笔线条有锯齿 一.文件:新建文件:预设尺寸:一般选择A3(8k),A4(16k),A5(32k)作业要求:A4A5 - 300dpi,像素 ...