有没有做高仿手表的网站,高端网站建设内容,个人网页制作总结,网站信息内容建设自查报告Socket是封装了TCP协议#xff0c;让我们更容易使用TCP协议。TCP协议在OSI模型中属于四层协议#xff0c;即传输层协议。 TCP#xff0c;中文叫传输控制协议#xff0c;它是一种面向连接的协议#xff0c;就是说它通信前必须先连接#xff0c;再能通信。设计TCP这种协议的…Socket是封装了TCP协议让我们更容易使用TCP协议。TCP协议在OSI模型中属于四层协议即传输层协议。 TCP中文叫传输控制协议它是一种面向连接的协议就是说它通信前必须先连接再能通信。设计TCP这种协议的目的是为了实现在网络中传输数据包所以几乎所有网络编程都会涉及TCP协议就连HTTP协议也是基于TCP来完成数据的传输的。
说TCP是面向连接还有一层意思除了在传输之前需要在源端和目的端建立连接之外它会一直接维持连接的状态。
如果你给一个大数据包TCP传送TCP会先把这个大数据包拆成多个小的数据包再传送出去目的端接收这些小数据后组成这个大数据包再给到对应的应用程序。
用TCP通信的架构几乎都是客户端-服务端这种模式在这种模式中客户端首先主动向服务端发起通信请求这个请求就是要先和服务端建立连接。 接下来我们会用C语言实现Socket的客户端和服务端。同时我们会价一些C语言的知识。
头文件介绍 stdio.h 这个文件头文件是标准的输入输出Standard Input Output。这个头文件主要涉及文件相关的输入输出操作。典型的方法printf() scanf()getc(), putc()。怎么理解这里文件呢在Linux有一个基本的原则键盘、显示等这些操作都会作为文件来对待。事实上键盘输入是默认的stdin文件流显示输出是默认的stdout文件流。 stdlib.hStandard Library标准库主要涉及内存相关的操作典型的方法malloc() free()abort()exit() string.h: 这个头文件涉及了许多字符数组字符串的操作如strlen() unistd.h 这个是Linux/Unix系统的内置头文件涉及了许多系统调用的原型包含了许多标准符号常量和类型如getuid() setuid() sleep()等等 sys/socket.h 这是主要的socket头文件socket编程都要引入这个头文件。 arpa/inet.h 这个头文件涉及了网络操作的定义
Socket 客户端
1.创建socket
short create_socket(){short sock;printf(Create a socket\n);sock socket(AF_INET,SOCK_STREAM,0);return sock;
}这里用到sys/socket.h头文件中的socket()函数AF_INET宏也定义在sys/socket.h头文件里代表IPv4地址AF代表了Address Family地址族。类似的还有AF_INET6IPv6地址等SOCK_STREAM这个宏也定义在sys/socket.h头文件里它代表的是字节流socket类似的有SOCK_SEQPACKET顺序包socket、SOCK_RAW原始协议接口、SOCK_DGRAM数据报socket。
这里调用了一个系统调用int socket(int domain, int type, int protocol);
domain参数指定了一个通信域选择用于通信的协议族所有可用的协议族都定义在sys/socket.h本例选择了AF_INETtype参数指定socket的类型这个类型确定了通信的语义。简单地来说就是确定了对一系列符号的理解通常语义都有特定的环境。举个例子为说吧假如玩个游戏出现100就是举左手出现50就举右手那么参与这个游戏的人都会根据对应的数字举相应的手。这就是参与游戏人对100和50的意义的理解。在socket中这个type的参数的选择就确定了对数据的意义的理解方式。protocol参数指定了特定的用于socket的协议一般来说在一个给定的协议族中只存在一个协议能够支持特定类型的socket在这种情况可以设置为0 。特殊情况下可能在domain指定的协议族中存在多个协议能够支持特定类型的socket此时我们可以指定要哪个协议来支持给定类型的socket通过设置protocol这个参数来选用协议族中特定的协议。
本例中创建了一个IPv4协议族的字节流socket。
sock socket(AF_INET,SOCK_STREAM,0);2.连接服务端 int connect_socket(int sock) {int ret -1;int server_port 60000;struct sockaddr_in remote {0};//服务器地址remote.sin_addr.s_addr inet_addr(127.0.0.1);//socket的协议族这里是IPv4协议族remote.sin_family AF_INET;//服务器端口号remote.sin_port htons(server_port);//连接服务器端ret connect(sock,(struct sockaddr *)remote,sizeof(struct sockaddr));return ret;
}sockaddr_in结构体详情 typedef uint32_t in_addr_t;typedef uint16_t in_port_t; // 这就是为什么端口号最大只到65535typedef /* ... */ socklen_t; //socket地址的长度至少是一个32比特的整型typedef /* ... */ sa_family_t;// 这是一个无符号整型它用于描述socket的协议族struct in_addr {in_addr_t s_addr;};struct sockaddr {sa_family_t sa_family; /* Address family */char sa_data[]; /* Socket address */};struct sockaddr_in {sa_family_t sin_family; /* AF_INET */in_port_t sin_port; // 端口号struct in_addr sin_addr; /* IPv4 address */};在arpa/inet.h有两个方法
inet_addr()函数将标准IPv4点分十进制格式的字符串转换为适合作为Internet地址使用的整数值。htons()函数将返回从主机字节顺序转换为网络字节顺序的参数值。这里就是将端口值转换成网络字节序。
连接socket的函数int connect(int socket, const struct sockaddr *address, socklen_t address_len);
socket参数是一个与socket关联的文件描述符。这也是一个说明在linux一切皆文件的概念的例子。因为读取另一端的socket发送过来的数据是通过这个文件描述address参数对等端的地址在这上述代码中这个值就是服务端的地址address_len参数指的是address参数的struct sockaddr 结构体的长度。
(struct sockaddr *)remote ,这里将结构体struct sockaddr_in的变量remote强转成结构体struct sockaddr类型指针这里指针只能看到有限的范围里的值不需要给它看到的就被自然的屏蔽掉了。我们举些列子来说明一下
struct A {int name;int age;int ho;
};struct B {int name;int age;
}int main(int agrc, char*argv[]){struct A a { 11, 22, 33 };struct B *b (struct B *)a;...return 0;
}在上述列子中b指针指向变量a所在的区域只是b能看到的范围是受自己的成员大小限制的如果结构体B的成员的大小能够大于或等于结构体A的大小那么a变量的内存空间b指针都能够看到。比如上述例子中structure A的大小是3个int大小structure B的大小是2个int的大小那么structure B只能看到a变量的前两个int第三个是没有办法看到。与结构体里的成员名称是没有关系的。也就是哪怕structure B定义成下面
struct B {int aaa;int bbb;
}b-aaa访问的仍然是a的nameb-bbb访问的仍然是a的age。另外还有一个特点假设
struct B {long aaa;long bbb;
}struct B的成员类型变成了long它是8个字节的长度也就是说b-aaa就访问了a变量的name和age变量把name的4个字节和age的4个字节都读出来了有时你不能确定name的4个字节是在age的4个字节前还是后面 因为这里还有大小端的问题。
3.用socket向服务端发送信息
int socket_send(int sock, char*req,short len){int ret -1;struct timeval tv;tv.tv_sec 20;tv.tv_usec 0;if(setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char *)tv,sizeof(tv)) 0 ){printf(Failed to set Send Timeout\n);return -1;}ret send(sock,req,len,0);return ret;
}typedef /* ... */ time_t; // 秒整型typedef /* ... */ suseconds_t; // 毫秒有符号整型typedef /* ... */ useconds_t; // 毫秒无符号整型struct timeval {time_t tv_sec; /* Seconds */suseconds_t tv_usec; /* Microseconds */};int setsockopt(int sockfd, int level, int optname, const void optval[.optlen], socklen_t optlen);这个函数可以用来控制socket的一些行为具体是哪些行为和怎么控制是由optnameoptval来决定的如设置缓冲区大小等等level是指定这些行为驻留的协议级别。本例中我们用这个函数设置发送超时具体参数描述如下
sockfd这个是socket对应的文件描述符level这个是指定操作选项所在的级别并且必须指定该选项的名称。在socket API级别操作选项指定的级别就是SOL_SOCKET。对于其他级别的操作选项需要提供控制这个选项的协议的协议号。如某个选项是被TCP协议解释的那么级别应该设置为IPPROTO_TCP(定义在netinet/in.h中。optnameoptname和任何指定的选项不经解释地传递给用于解释的适当协议模块。sys/socket.h定义了socket级别的一些选项。本例中SO_SNDTIMEO就是超时的选项。optvaloptlen是用于提供选项值的访问的在本例中就是用来访问设定的超时值。
接下来我们就要利用send函数向服务端的socket发送信息send函数的原型如下 ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
sockfd创建好的socket的文件描述符buf 这是要发送信息的缓冲区发送的内容就放在里面len缓冲区中要发送内容的长度flags 对于一个已经连接的socket来说这里指定成NULL或0即可。
接收服务端socket发送过来的消息
int socket_receive(int sock, char*resp,short len){int ret -1;struct timeval tv;tv.tv_sec 20;tv.tv_usec 0;//设置接收超时if(setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char*)tv,sizeof(tv)) 0){printf(Failed to set receive timeout\n);return -1;}ret recv(sock,resp,len,0);printf(Response %s\n,resp);return ret;
}ssize_t recv(int sockfd, void *buf, size_t len, int flags);用这个系统调用来接收消息
sockfd创建好的socket的文件描述符buf接收消息的缓冲区len缓冲区大小flags 对于一个已经连接的socket来说这里指定成NULL或0即可。
4.客户端socket的main函数
int main(int argc,char*argv[]){int sock;int read_size;char send_to_server[100] {0};char recv_from_server[100] {0};//创建socketsock create_socket();if(sock -1){printf(Could not create a socket\n);return 1;}printf(Socket is created\n);//将客户端socket连接到服务端socketif(connect_socket(sock) 0){printf(Connect to server socket failed\n);return 1;}printf(Successfully connected to server\n);printf(Enter a message: \n);//等待用户输入要发送到服务端socket的消息fgets(send_to_server,100,stdin);//用客户端socket发送消息到服务端socketsocket_send(sock,send_to_server,strlen(send_to_server));//客户端socket收到服务端socket的响应read_size socket_receive(sock,recv_from_server,100);printf(Server response: %s\n,recv_from_server);//关闭客户端socketclose(sock);shutdown(sock,0);//不再读shutdown(sock,1);//不再写shutdown(sock,2);//不再读写return 0;
}int shutdown(int socket, int how); 参与
socket参数是创建好的socket的文件描述符how参数指定关闭的类型SHUT_RD关闭接收SHUT_WR关闭发送SHUT_RDWR关闭接收和发送操作
关于close和shutdown的区别可以参考这里
服务端socket
1.创建socket
short create_socket(void){short sock;printf(Create a server socket\n);sock socket(AF_INET,SOCK_STREAM,0);return sock;
}2.绑定socket
int bind_created_socket(int sock){int ret -1;int port 60000;struct sockaddr_in remote {0};remote.sin_addr.s_addr htonl(INADDR_ANY);remote.sin_port htons(port);ret bind(sock,(struct sockaddr *)remote,sizeof(remote));return ret;
}参考
INADDR_ANY (0.0.0.0)表示socket绑定到任何地址
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);当我们用socket()这个函数创建了一个socket后这个socket就存在于一个命名空间中但是没有任何地址分配给它。bind()这个函数要将addr指定的地址分配给这个socket。
sockfd 参数socket文件描述符addr将要绑定到socket的地址addrlen地址长度单位是字节
3. 服务器端socket的main函数
int main(int argc,char*argv[]){int sock_server;int sock;int client_len;int read_size;struct sockaddr_in server;struct sockaddr_in client;char client_message[100] {0};char message[100] {0};const char *msg Hello!\n;sock_server create_socket();if(sock_server -1){printf(Could not create a socket\n);return 1;}printf(Successfully created a socket\n);if(bind_created_socket(sock_server) 0){perror(Bind failed\n);return 1;}printf(Bind done\n);listen(sock_server,3);while(1){printf(Waiting for incoming connections...\n);client_len sizeof(struct sockaddr_in);sock accept(sock_server,(struct sockaddr *)client,(socklen_t*)client_len);if(sock 0){perror(accept failed\n);return 1;}printf(Connection accepted\n);memset(client_message, \0,sizeof client_message);memset(message, \0, sizeof message);if(recv(sock,client_message,100,0) 0){printf(recv failed);break;}printf(Client response: %s\n,client_message);if(strcmp(msg,client_message) 0){strcpy(message,Hi there!);} else {strcpy(message,Invalid Message!);}if(send(sock,message,strlen(message),0) 0){printf(Send failed\n);return 1;}close(sock);sleep(1);}
int listen(int sockfd, int backlog);这个函数会将socket标记为一个被动的socket也就是说作为一个socket它会在accpt函数中被用于接受传入的连接请求。backlog是指定等待连接请求的队列大小。比如本例设置了3那么也就是队列中等待连接请求处理的数量最大只能有3个有一个客户端socket请求连接如果此时队列已满也就是已有3个在队列中那么这个新的请求就不会被处理对应的客户端socket就会收到一个错误如ECONNREFUSED。
int accept(int sockfd, struct sockaddr *_Nullable restrict addr, socklen_t *_Nullable restrict addrlen); 这个函数会从等待连接的队列中为监听的socket就是listen()函数参数中那个传入的socket取出第一个连接请求来创建一个新的socket返回的值就是新的socket的文件描述符
sockfd 就是socket()函数创建的socket文件描述符这个socket通过bind()函数绑定了一个本地地址并在listen()函数参数传入被标记为被动的socket监听连接。addr参数是一个指向sockaddr结构体的指针这个结构体会用对等端发起连接请求的socket的地址来直充如果addr没有被填充即addr是NULL这种情况下addrlen也不会被使用也是NULL。addrlen是addr结构体的长度如果addr是NULL那么这个addrlen也是NULL。
void *memset(void s[.n], int c, size_t n);这个函数用常数字节c填充s指针指向的内存区域的前n个字节。