socket套接字网络通信代码在最下面,可自取
三种套接字类型
套接字有三种类型:
①流式套接字(SOCK_STREAM)
②数据报套接字(SOCK_DGRAM)
③原始套接字
个人理解:流式套接字采用TCP连接方式,数据报套接字采用UDP连接方式
套接字的一些基本数据结构
struct sockaddr,该结构用来存储套接字地址
数据定义如下:
struct sockaddr{
unsigned short sa_family;//address族,一般使用AF_INET
char sa_data[14];//包含一些远程电脑的地址、端口和套接字的数目
}
②但是,该结构在套接字中一般用struct sockaddr_in结构来替代struct sockaddr,该结构定义如下:
sruct sockaddr_in{//后缀in代表Internet
short int sin_family;//address族,一般使用AF_INET
unsigned short int sin_port;//端口号
struct in_addr sin_addr;//Internet地址
unsigned char sin_zero[8];//添0,目的是和struct sockaddr的大小保持一致
}
③struct in_addr用于因特网地址,结构定义如下:
struct in_addr{
unsigned long s_addr;
}
网络转换函数
网络字节顺序转换函数
由于不同主机的存储有大端字节和小端字节的区别,而在网络传输需要进行统一顺序,这就要用到了网络转换函数。
注意:把数据发送到Internet之前,一定要把它的字节顺序从主机字节顺序转换到网络字节顺序。
htons() -- Host to NetWork Short 主机字节顺序转换为网络字节顺序
htonl() -- Host to NetWork Long 主机字节顺序转换为网络字节顺序
ntohs() -- NetWork to Host Short 网络字节顺序转换为主机字节顺序
ntohl() -- NetWork to Host Long 网络字节顺序转换为主机字节顺序
inet_addr(ip),把ip转换为网络字节,该函数得到的返回值已经是网络字节顺序了,不用再使用htons()函数进行转换了。
struct sockaddr_in my_addr;
my_addr.sin_addr.s_addr = inet_addr("192.111.69.52");
inet_ntoa()用于将in_addr类型的数据转换为一个字符串指针,并返回;
char *p;
p = inet_ntoa(my_addr.sin_addr);
printf("address: %s",p);//以 数字.数字.数字.数字 形式显示出来
基本套接字系统调用
以下系统调用需要经常用到:
- socket()
- bind()
- connect()
- listen()
- accpet()
- send()
- recv()
- sendto()
- recvfrom()
- close()
- shutdown()
以下是部分常用的系统调用具体说明,代码有详细说明:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
socket()系统调用用于取得套接字描述符;
其中domain一般设置为"AF_INET";
type则对应套接字的类型,"SOCK_STREAM"或"SOCK_DGRAM";
protocol只需要设置为0;
该函数返回一个你以后可以使用的套接字描述符
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,struct sockaddr* my_addr, int addrlen);
该函数用于将socket套接字描述符绑定一个机器上的端口;
sockfd为socket()函数返回的套接字描述符
my_addr是一个指向struct sockaddr的指针,一般可以将sockaddr_in对象的地址利用强制类型转换放在此处当做参数,如((struct sockaddr*)&my_addr);
addrlen可以设置为sizeof(struct sockaddr);
注意:使用bind绑定端口号时候,最好将端口参数设置大一点,小于1024的端口号都是被保留下来作为系统使用端口的,你可以使用1024以上的任何端口,一直到65535;
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,struct sockaddr *serv_addr,int addrlen)
sockfd和addrlen的取值和bind()中类似
serv_addr应该是一个存储远程计算机的IP地址和端口信息的结构;
最好对返回值进行检查,如果返回错误值-1表示发生了错误(无法连接到远程主机等);
#include <sys/socket.h>
int listen(int sockfd,int backlog);
listen()是等待别人连接,进行系统侦听请求的函数
sockfd是套接字描述符;
backlog是本地能够等待的未连接的最大数目,推荐使用SOMAXCONN宏;
执行完listen之后,将socket从主动套接字变为被动套接字
#include <sys/socket.h>
int accept(int sockfd, void* addr, int* addrlen);
当有客户从别的地方尝试使用connect()函数来连接某个已经在listen()的端口时候,accept()将返回一个新的套接字描述符,
代表这个连接。而最开始listen()的那个套接字描述符依旧在原来的端口上listen();
sockfd是正在listen()的套接字描述符;
addr指向着从远程连接过来的计算机的信息,是一个struct sockaddr_in结构的指针;
addrlen是本地的一个整型数值,其大小应该是sizeof(struct sockaddr_in);
从已连接队列返回第一个连接,如果已连接队列为空则阻塞
send()和recv()是基本的SOCK_STREAM套接字流进行通讯的函数
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd, const void* msg, int len, int flags);
sockfd表示已经建立连接的套接字描述符,如accept()返回的套接字描述符;
msg为发送信息的地址;
len是发送信息的长度;
flags为发送标记,一般设0;
send()函数的返回值为真正发送信息的长度;
int recv(int sockfd,void* buf,int len, unsigned int flags);
sockfd表示已经建立连接的套接字描述符,如accept()返回的套接字描述符;
buf是一个指针,指向能够存储数据的缓冲区;
len是缓存区最大尺寸;
flags为接受标记,一般设0;
recv()函数的返回值为真正接受信息的长度;
sendto()和recvfrom()函数是无连接UDP通讯时候使用的,此处不做详细介绍;
close(int sockfd);
使用标准的关闭文件的函数来对套接字描述符进行关闭操作;
执行close()函数之后,套接字将不会允许进行读写操作;
int shutdown(int sockfd, int how)
如果想对套接字的关闭进行进一步操作的话,可以使用shutdown()函数;
sockfd为套接字描述符;
how为控制方式变量:0不允许数据接受操作,1不允许数据发送操作,2不允许发送接受操作
在面向连接的协议程序中,服务器端按照以下流程执行:
- 调用socket()创建一个套接字
- 调用bind()把自己绑定在一个地址上
- 调用listen()函数侦听连接
- 调用accept()函数接受所有引入的请求
- 连接成功之后调用send()和recv()进行和客户机client的通信
在面向连接的协议程序中,客户端按照以下流程执行:
- 调用socket()创建一个套接字
- 调用bind()把自己绑定在一个地址上
- 调用connect()函数进行连接
- 连接成功之后调用send()和recv()进行和服务器server的通信
以下是一个关于cs模型中服务器端和客户端的通信的代码详解:
客户端server.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 4000
#define BACKLOG 10
#define MAXDATASIZE 100
int main()
{
char buf[MAXDATASIZE];
int sock_fd,sock_new;
//定义套接字描述符
//定义套接字地址对象
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
//初始化本地连接地址
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
//套接字描述符初始化
if((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket error!");
exit(1);
}
//绑定套接字描述符
if((bind(sock_fd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))) == -1)
{
perror("bind error!");
exit(1);
}
//listen()函数监听
if((listen(sock_fd,BACKLOG)) == -1)
{
perror("listen error!");
exit(1);
}
//accept()主循环
//进行listen()之后的套接字进行accept(),只有accept()之后才能进行收发数据
while(1)
{
//sin_size用于accept()中最后一个参数
int sin_size = sizeof(struct sockaddr_in);
if((sock_new = accept(sock_fd,(struct sockaddr*)&their_addr,&sin_size)) == -1)
{
perror("accpet error!");
continue;
}
printf("server:got connetion form %s\n",inet_ntoa(their_addr.sin_addr));
if(fork() == 0)//fork==0表示此处是子线程里面,子线程用于处理远处的连接
{
//发送数据
if((send(sock_new,"Hello!\n",7,0)) == -1)
{
perror("send error!\n");
close(sock_new);
exit(0);
}
//接受数据
int len;
int i = 0;
//char str[] = "------------------------------->>>>>\n";
printf("等待第%d次消息接受>>>\n",i+1);
while(1)
{
i++;
if((len = recv(sock_new,buf,MAXDATASIZE,0)) == -1)
{
perror("recv error!");
exit(0);
}
//接受数据打印
printf("第%d次收到消息,内容如下:\n",i);
buf[len] = '\0';
printf("%s",buf);
bzero(buf,MAXDATASIZE);
printf("等待第%d次消息接受>>>\n",i+1);
}
close(sock_new);
}
}
while(waitpid(-1,NULL,WNOHANG) > 0);
exit(0);
}
客户端client.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MYPORT 4000//端口号码
#define MAXDATASIZE 100//最大缓存空间
int main(int argc, char * argv[])
{
int sock_fd,numbytes;
char buf[MAXDATASIZE];
struct hostent* he;
struct sockaddr_in their_addr;
if(argc != 2)
{
fprintf(stderr,"usage:client hostname\n");
exit(1);
}
if((he = gethostbyname(argv[1])) == NULL)
{
herror("gethostbyname");
exit(1);
}
//使用socket()函数获取可用的套接字
if((sock_fd = socket(AF_INET,SOCK_STREAM,0) == -1))
{
perror("socket error!");
exit(1);
}
//对their_addr进行参数设置
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(MYPORT);
//这个地方在琢磨一下!!
their_addr.sin_addr = *((struct in_addr*)he->h_addr);
bzero(&(their_addr.sin_zero),8);//置零
//connect()函数用于连接设定好的their_addr地址
if((connect(sock_fd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr))) == -1)
{
perror("connect error!");
exit(1);
}
//利用recv()函数接受数据
if((numbytes = recv(sock_fd,buf,MAXDATASIZE,0)) == -1)
{
perror("recv error!");
exit(1);
}
buf[numbytes] = '\0';
printf("Recvived:%s\n",buf);
int i = 0;
//利用send()发送数据
while(1)
{
i++;
printf("第%d次输入数据:\n",i);
//fflush(stdout);
scanf("%s",buf);
if(send(sock_fd,buf,sizeof(buf),0) == -1)
{
perror("send error!");
exit(1);
}
bzero(buf,MAXDATASIZE);
printf("第%d次成功发送\n",i);
fflush(stdout);
}
//关闭打开的socket套接字
close(sock_fd);
return 0;
}
具体使用方法:
- 首先用gcc对server.c编译,然后启动server程序
- 另外打开一个终端,用gcc对client.c编译,然后在命令行中输入以下命令 telnet localhost 4000 ,连接之后在client终端中输入消息,即可在server中得到输出。得到以下输出结果
t@t:~/Desktop/c_s/client$ telnet localhost 4000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!
hello world!
you are the best!
t@t:~/Desktop/c_s/server$ ./server
server:got connetion form 127.0.0.1
等待第1次消息接受>>>
第1次收到消息,内容如下:
hello world!
等待第2次消息接受>>>
第2次收到消息,内容如下:
you are the best!
等待第3次消息接受>>>
如果看完了并觉得有收获的话,走过路过留个赞,磕头谢!!