用socket套接字实现进程间的一对一通信
TCP协议通信
TCP协议通信所使用的函数和步骤操作都备注在程序中:
服务器端程序:server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#define SERV_PORT 8900
int main(void )
{
int ret;
int socket_serv;
int socket_client;
struct sockaddr_in serv_addr;
struct sockaddr_in client_addr;
socklen_t addrlen;
char buf[128];
/*第一步:创建socket
int socket(int domain, int type, int protocol);
domain:
AF_INET:使用ipv4进行传输,用于tcp和udp通信
AF_UNIX:用于本地进程通信
AF_PACKET:tcp和udp都没法满足数据传输要求,可以自定义协议通信方式
type:
SOCK_STREAM:tcp(流式套接字)提供面向连接、字节流、安全可靠的通信
SOCK_DGRAM:udp(数据包套接字)提供无面向连接、数据包、不安全可靠的通信
SOCK_RAW:raw(原始套接字)
protocol: 0
返回值:
>0:返回套接字文件描述符
-1:创建套接字失败,并设置errno的值
*/
socket_serv = socket(AF_INET, SOCK_STREAM, 0);
if(socket_serv < 0){
perror("serv socket err");
return -12;
}
/*
第二步:绑定bind:ip + port(指定IP地址和端口号)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd:套接字文件描述符,由socket()函数的返回值得到
addr:ip + port
一般不使用struct sockaddr类型,而是使用以下结构体:
struct sockaddr_in {
sa_family_t sin_family;直接填AF_INET
in_port_t sin_port;端口号,以网络字节序存放
struct in_addr sin_addr;ip地址的结构体里的成员以网络字节序存放IP地址
};
struct in_addr {
uint32_t s_addr;ip地址,以网络字节序存放
};
注:由于结构体类型不一样,所以在使用的时候还是要将我们初始化的struct sockaddr_in类型结构体变量取地址后强制转化为sockaddr *类型的结构体!
addrlen:struct sockaddr_in结构体类型的字节大小,比如sizeof(addr)
返回值:
0:表示绑定成功
-1:表示绑定失败,并设置errno的值
*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);//htons()将计算机字节序转换为网络字节序
//serv_addr.sin_addr.s_addr = inet_addr( "192.168.3.137");//inet_addr()将字符型数据转换为整型
ret = bind(socket_serv,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("bind err");
return -13;
}
/*
第三步:监听listen
int listen(int sockfd, int backlog);
sockfd: 套接字文件描述符,由socket()函数的返回值得到
backlog:表示允许有2*backlog+1个连接同时打进来,所以一般写5个
返回值:int类型
0:表示监听成功成功
-1:表示监听失败,并设置errno的值
*/
ret = listen(socket_serv,5);
if(ret <0){
perror("listen err");
return -14;
}
/*
第四步:接听发送过来的连接accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr:函数会将对方的地址相关信息回填到该结构体中,同样是struct sockaddr_in类型的结构体,所以同样要取地址后强制转换为sockaddr *类型
addrlen: 对方地址长度
struct sockaddr_in {
sa_family_t sin_family;//直接填AF_INET
in_port_t sin_port;//端口号,以网络字节序存放
struct in_addr sin_addr;//ip地址的结构体里的成员以网络字节序存放IP地址
};
struct in_addr {
uint32_t s_addr;//ip地址,以网络字节序存放
};
返回值:int类型
>0:表示连接成功,返回一个全新的套接字(另一端的套接字),用于后面的通信
-1:表示连接失败,并设置errno的值
*/
addrlen = sizeof(client_addr);
socket_client = accept(socket_serv,(struct sockaddr *)&client_addr,&addrlen);
if(socket_client <0){
perror("accept err:");
return -16;
}
while(1){
/*使用新的socket接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
buf:接收数据存放的空间
len: buf的大小
flags: 0
返回值:int类型
>0:实际接收到的数据字节大小
-1:接收失败,并设置errno
==0:对方主动或者意外挂掉连接
*/
memset(buf,0,sizeof(buf));
ret = recv(socket_client,buf,sizeof(buf),0);
if(ret <0){
perror("recv err:");
return -17;
}else if(ret == 0){
printf("peer close it\n");
close (socket_client);
return 0;
}
printf("serv get data:%s\n",buf);
/*使用新的套接字发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
len:要发送的数据的字节大小
返回值
>=0:实际发送的字节大小,int类型
-1:发送数据失败,并设置errno
*/
strcpy(buf,"your message is got");
ret = send(socket_client,buf,sizeof(buf),0);
if(ret <0){
perror("send err:");
return -18;
}
}
close(socket_client);
}
客户端程序:client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define SERV_PORT 8900
int main(void )
{
int ret;
int socket_serv;
struct sockaddr_in serv_addr;
char buf[128];
/*第一步,创建socket
*/
socket_serv = socket(AF_INET, SOCK_STREAM, 0);
if(socket_serv < 0){
perror("client socket err");
return -12;
}
/*向服务器发起连接请求connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
addr:要连接的终端的IP地址及其相关信息,写法同bind函数和accept函数
返回值:
0:表示连接成功
-1:表示连接失败,并设置errno
*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = inet_addr( "192.168.3.137");
ret = connect(socket_serv,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("connect err");
return -12;
}
while(1){
strcpy(buf,"client info : nihaoma?");
ret = send(socket_serv,buf,sizeof(buf),0);
if(ret <0){
perror("client send err:");
return -18;
}
memset(buf,0,sizeof(buf));
ret = recv(socket_serv,buf,sizeof(buf),0);
if(ret <0){
perror("client recv err:");
return -17;
}else if(ret == 0){
printf("peer close it\n");
close (socket_serv);
return 0;
}
printf("client get data:%s\n",buf);
sleep(1);
}
close(socket_serv);
return 0;
}
上述程序中,实现了两个终端间的一对一网络通信,并成功能互相传输单个数据。那么,要实现两个终端间的多数据传输就要将多个数据作为结构体成员,封装到结构体中,在两个终端进行通信时,直接发送和接收已经定义好的结构体即可。在写网络通信程序之前就要先定义一个结构体类型,结构体成员中定义需要传输的数据变量(成员),而且为了让程序中的每一个发送和接收的函数都使用结构体,要将结构体类型定义为全局变量。因此,为方便编程,直接将结构体定义在一个头文件中,在程序中引用这个头文件。
值得注意的是:数据在网络通信过程中,需要区分大端模式和小端模式。int类型、short类型和long类型的数据都要注意字节序的问题,在发送之前要将数据转换为网络字节序,在接收之后要转换为主机字节序。另外,char类型的数据不需要注意字节序的问题,可以不用来回转换,所以一般在网络编程中能使用char类型的数据就使用char类型。还要注意的是,在网络中的数据传输绝对不能有浮点型(float)数据!
TCP协议端对端多数据网络通信程序
头文件com.h
#ifndef TCP_STRUCT_TEST_H
#define TCP_STRUCT_TEST_H
#define PORT 7200
struct data_package
{
int temp;
int hum;
short cnt;
char info[64];
}__attribute__((packed));
#endif
/*
__attribute__((packed));表示告诉编译器, 不要对该结构进行任何字节对齐操作,保持原有的数据字节序。
一般情况下,在定义结构体后,gcc编译器要对结构体中的数据进行一个字节对齐的操作。
什么是字节对齐?指在分配内存时最小的单位,由编译器决定。总空间=n*字节对齐数,会按顺序分配内存,这样会使数据尽可能少占用内存,但是不能更改数据的顺序。比如是4字节对齐,最小内存为4字节或者4的倍数个字节。结构体中的成员最大内存为10字节,但系统会分配给结构体12个字节。
*/
服务器程序server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include "com.h"
int main(void)
{
int socket_serv;
int res;
int client;
struct sockaddr_in sockaddr;
struct sockaddr_in client_addr;
int resize;
socklen_t client_len;
struct data_package data;
socket_serv = socket(AF_INET,SOCK_STREAM,0);
if(socket_serv < 0)
{
perror("socket()");
exit(-1);
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = PORT;
//sockaddr.sin_addr.s_addr = inet_addr("192.168.3.188");
sockaddr.sin_addr.s_addr = INADDR_ANY;
res = bind(socket_serv,(struct sockaddr *)&sockaddr,sizeof(sockaddr));
if(res < 0)
{
perror("bind()");
exit(-2);
}
res = listen(socket_serv,5);
if(res < 0)
{
perror("listen()");
exit(-3);
}
client_len=sizeof(client_addr);
client = accept(socket_serv,(struct sockaddr *)&client_addr,\
&client_len);
if(client < 0)
{
perror("accept()");
exit(-4);
}
printf("connect port:%d,address:%s\n",ntohs(client_addr.sin_port),\
inet_ntoa(client_addr.sin_addr));
while(1)
{
//resize = recv(client,buf,BUFSIZE,0);
resize = recv(client,&data,sizeof(data),0);//取结构体的地址及其字节大小
/*
接收客户端发过来的数据时,先要定义一个同将要接收数据一样的数据类型变量,用于存放接收过来的数据。在这里接收的数据是一个结构体,那么就要先定义一个相同的结构体类型的结构体变量。
*/
if(resize < 0)
{
perror("recv()");
exit(-5);
}
else if(resize == 0)
{
printf("peer close!\n");
close(client);
exit(0);
}
data.temp = ntohl(data.temp);
data.hum = ntohl(data.hum);
data.cnt = ntohs(data.cnt);
printf("[%d] temperture:%d,humidty:%d,%s\n",data.cnt,data.temp,data.hum,data.info);
strcpy(data.info,"get your message!");
//data.cnt = ntohs(data.cnt);
data.cnt++;
data.cnt = htons(data.cnt);
data.temp = htonl(data.temp);
data.hum = htonl(data.hum);
res = send(client,&data,sizeof(data),0);
if(res < 0)
{
perror("send()");
exit(-6);
}
}
close(client);
exit(0);
}
客户端程序client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include "com.h"
#define BUFSIZE 128
int main(void)
{
int socket_client;
int res;
struct sockaddr_in sockaddr;
int resize;
socklen_t client_len;
struct data_package data_c;
socket_client = socket(AF_INET,SOCK_STREAM,0);
if(socket_client < 0)
{
perror("socket()");
exit(-1);
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = PORT;
sockaddr.sin_addr.s_addr = inet_addr("149.28.27.109");
res = connect(socket_client,(struct sockaddr *)&sockaddr,sizeof(sockaddr));
if(res < 0)
{
perror("bind()");
exit(-2);
}
data_c.temp = 28;
data_c.hum = 59;
data_c.cnt = 1;
strcpy(data_c.info,"message have sended!");
while(1)
{
data_c.temp = htonl(data_c.temp);
data_c.hum = htonl(data_c.hum);
data_c.cnt = htons(data_c.cnt);
res = send(socket_client,&data_c,sizeof(data_c),0);
if(res < 0)
{
perror("send()");
exit(-6);
}
memset(&data_c,0,sizeof(data_c));
resize = recv(socket_client,&data_c,sizeof(data_c),0);
if(resize < 0)
{
perror("recv()");
exit(-5);
}
data_c.temp = ntohl(data_c.temp);
data_c.hum = ntohl(data_c.hum);
data_c.cnt = ntohs(data_c.cnt);
printf("[%d] temperture:%d,humidty:%d,%s\n",data_c.cnt,data_c.temp,\
data_c.hum,data_c.info);
sleep(1);
}
close(socket_client);
exit(0);
}
UDP协议的socket通信编程
不多说,与前面的程序有点类似,直接上程序。
基于UDP协议的端对端(一对一)通信程序如下:
头文件com,h
#ifndef UDP_STRUCT_TEST_H
#define UDP_STRUCT_TEST_H
#define PORT 5549
struct mesdata
{
int temp;
int hum;
short cnt;
char des[64];
}__attribute__((packed));
#endif
服务器程序server.h
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include "com.h"
int main(void)
{
int sock_serv;
struct sockaddr_in servaddr;
int res;
struct mesdata data;
struct sockaddr_in cliaddr;
int recv;
socklen_t clilen;
sock_serv = socket(AF_INET,SOCK_DGRAM,0);
if(sock_serv < 0)
{
perror("socket()");
exit(-1);
}
servaddr.sin_family = AF_INET;
servaddr.sin_port = PORT;
servaddr.sin_addr.s_addr = INADDR_ANY;
res = bind(sock_serv,(struct sockaddr *)&servaddr,sizeof(servaddr));
if(res < 0)
{
perror("bind()");
exit(-2);
}
while(1)
{
clilen = sizeof(cliaddr);
memset(&data,0,sizeof(data));
recv = recvfrom(sock_serv,&data,sizeof(data),0,\
(struct sockaddr *)&cliaddr,&clilen);
if(recv < 0)
{
perror("recvfrom()");
exit(-3);
}
else if(recv == 0)
{
printf("closeed!\n");
close(sock_serv);
exit(0);
}
data.cnt = ntohs(data.cnt);
data.cnt++;
data.cnt = htons(data.cnt);
res = sendto(sock_serv,&data,recv,0,(struct sockaddr *)&cliaddr,\
sizeof(cliaddr));
if(res < 0)
{
perror("sendto()");
exit(-5);
}
}
close(sock_serv);
exit(0);
}
客户端程序client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include "com.h"
int main(void)
{
int sock_cli;
struct sockaddr_in cliaddr;
int res;
int recv;
struct mesdata clidata;
socklen_t clilen;
sock_cli = socket(AF_INET,SOCK_DGRAM,0);
if(sock_cli < 0)
{
perror("socket()");
exit(-1);
}
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = PORT;
cliaddr.sin_addr.s_addr = inet_addr("192.168.3.188");
clidata.temp = 28;
clidata.hum = 64;
clidata.cnt = 0;
strcpy(clidata.des,"the imformation is");
while(1)
{
clidata.temp = htonl(clidata.temp);
clidata.hum = htonl(clidata.hum);
clidata.cnt = htons(clidata.cnt);
res = sendto(sock_cli,&clidata,sizeof(clidata),0,\
(struct sockaddr *)&cliaddr,sizeof(cliaddr));
if(res < 0)
{
perror("sendto()");
exit(-5);
}
clilen = sizeof(cliaddr);
memset(&clidata,0,sizeof(clidata));
recv = recvfrom(sock_cli,&clidata,sizeof(clidata),0,\
(struct sockaddr *)&cliaddr,&clilen);
if(recv < 0)
{
perror("recvfrom()");
exit(-3);
}
else if(recv == 0)
{
printf("closeed!\n");
exit(0);
}
clidata.temp = ntohl(clidata.temp);
clidata.hum = ntohl(clidata.hum);
clidata.cnt = ntohs(clidata.cnt);
printf("[%d] %s temperture=%d,hum=%d\n",clidata.cnt,clidata.des,\
clidata.temp,clidata.hum);
sleep(1);
}
close(sock_cli);
exit(0);
}