如题,从一个简单示例去分析socket的编程。
首先给出源码
server.c
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main()
{
int sfp,nfp;
struct sockaddr_in s_add,c_add;
int sin_size;
unsigned short portnum=0x8888;
printf("Hello,welcome to my server !\r\n");
sfp = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfp)
{
printf("socket fail ! \r\n");
return -1;
}
printf("socket ok !\r\n");
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr=htonl(INADDR_ANY);
s_add.sin_port=htons(portnum);
if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
{
printf("bind fail !\r\n");
return -1;
}
printf("bind ok !\r\n");
if(-1 == listen(sfp,5))
{
printf("listen fail !\r\n");
return -1;
}
printf("listen ok\r\n");
while(1)
{
sin_size = sizeof(struct sockaddr_in);
nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
if(-1 == nfp)
{
printf("accept fail !\r\n");
return -1;
}
printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n",ntohl(c_add.sin_addr.s_addr),ntohs(c_add.sin_port));
if(-1 == write(nfp,"hello,welcome to my server \r\n",32))
{
printf("write fail!\r\n");
return -1;
}
printf("write ok!\r\n");
close(nfp);
}
close(sfp);
return 0;
}
client.c
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main()
{
int cfd;
int recbytes;
int sin_size;
char buffer[1024]={0};
struct sockaddr_in s_add,c_add;
unsigned short portnum=0x8888;
printf("Hello,welcome to client !\r\n");
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == cfd)
{
printf("socket fail ! \r\n");
return -1;
}
printf("socket ok !\r\n");
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr= inet_addr("192.168.3.124");
s_add.sin_port=htons(portnum);
printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port);
if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
{
printf("connect fail !\r\n");
return -1;
}
printf("connect ok !\r\n");
if(-1 == (recbytes = read(cfd,buffer,1024)))
{
printf("read data fail !\r\n");
return -1;
}
printf("read ok\r\nREC:\r\n");
buffer[recbytes]='\0';
printf("%s\r\n",buffer);
getchar();
close(cfd);
return 0;
}
gcc server.c -o server
gcc client.c -o client
下面对服务器端和客户端进行分析
server.c中
------------------------------------------------------------------------
第一个函数
sfp = socket(AF_INET, SOCK_STREAM, 0);
domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX 和 AF_INET 等).
AF_UNIX 只能够用于单一的 Unix 系统进程间通信,而 AF_INET 是针对 Internet 的,因而可以允
许在远程 主机之间通信(当我们 man socket 时发现 domain 可选项是 PF_*而不是 AF_*,因
为 glibc 是 posix 的实现 所以用 PF 代替了 AF,不过我们都可以使用的).
type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM 等) SOCK_STREAM
表明我们用的是 TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流. SOCK_DGRAM
表明我们用的是 UDP 协议,这样只会提供定长的,不可靠,无连接的通信.
protocol:由于我们指定了 type,所以这个地方我们一般只要用 0 来代替就可以了
socket 为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,看 errno 可知道出错
的详细情况.
extern void bzero(void *s, int n);将s为地址的n个字符置0;
一个小栗子
#include <string.h>
main()
{
struct
{
int a;
char s[5];
float f;
} tt;
char s[20];
bzero(&tt,sizeof(tt)); // struct initialization to zero
bzero(s,20);
return 0;
}
-----------------------------------------------------------------------
第三个函数if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
int bind(int sock_fd,struct sockaddr_in *my_addr, int addrlen);
功能说明:
参数说明:
my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;
struct sockaddr_in结构类型是用来保存socket信息的:
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
看看s_addr是什麽,
struct sockaddr_in s_add,c_add;是sockaddr这个结构体,但是怎么会强制转换成struct sockaddr这个结构体
来分别看看这两个结构体
struct sockaddr //16bytes
{
unsigned short sa_family; //2bytes
char sa_data[14]; //14bytes
}
struct sockaddr_in //16bytes
{
unsigned short sin_family; //2bytes
unsigned short sin_port; //2bytes
struct in_addr sin_addr; //4bytes
unsigned char sin_zero[8]; //8bytes
}
struct in_addr
{
unsigned int s_addr;
}
可以看出sockaddr和sockaddr_in的字节数是一致的,在tcpip中我们所写ip端口呀都是合并起来用的,而为了输入方便,在_in结构体中分开,使用时再强制转换。
s_add.sin_family=AF_INET;
s_add.sin_port=htons(portnum);
s_add.sin_addr.s_addr=htonl(INADDR_ANY);
上面为初始化这个结构体,看看htons这个函数
我们知道网络字节顺序是大端的,而主机字节顺序是小端的,所以有一组函数用于两者的转化
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
在这四个转换函数中,h 代表 host , n 代表 network . s 代表 short , l 代表 long 第一个函数的意义是将本机器上的 long 数据转化为网络上的 long. 其他几个函数的意义也差不多.
表示可以和任何的主机通信
----------------------------------------------------
第四个函数if(-1 == listen(sfp,5))
sockfd:是 bind 后的文件描述符.
backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示
可以介绍的排队长度. listen 函数将 bind 的文件描述符变为监听套接字.返回的情况和 bind 一
样.
默认情况下,内核会认为socket函数的创建的描述符对应于主动套接字,调用listen函数告诉内核,描述符是被服务器而不是客户端使用的
------------------------------------------------
到这服务器的初始工作就完成了,等到客户端连接
先来看看client.c
--------------------------------------------------------
第一个函数
cfd = socket(AF_INET, SOCK_STREAM, 0);
一样去理解;
---------------------------------------------------------
第二个函数
bzero(&s_add,sizeof(struct sockaddr_in));
一样去理解
--------------------------------------------------------
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr= inet_addr("192.168.3.124");
s_add.sin_port=htons(portnum);
这里有出现了inet_addr
IP地址转换
有三个函数将数字点形式表示的字符串IP地址与32位网络字节顺序的二进制形式的IP地址进行转换
(1) unsigned long int inet_addr(const char * cp):该函数把一个用数字和点表示的IP地址的字符串转换成一个无符号长整型,如:struct sockaddr_in ina
ina.sin_addr.s_addr=inet_addr("202.206.17.101")
该函数成功时:返回转换结果;失败时返回常量INADDR_NONE,该常量=-1,二进制的无符号整数-1相当于255.255.255.255,这是一个广播地址,所以在程序中调用iner_addr()时,一定要人为地对调用失败进行处理。由于该函数不能处理广播地址,所以在程序中应该使用函数inet_aton()。
(2)int inet_aton(const char * cp,struct in_addr * inp):此函数将字符串形式的IP地址转换成二进制形式的IP地址;成功时返回1,否则返回0,转换后的IP地址存储在参数inp中。
(3) char * inet_ntoa(struct in_addr in):将32位二进制形式的IP地址转换为数字点形式的IP地址,结果在函数返回值中返回,返回的是一个指向字符串的指针。
-----------------------------------------------------------------------
第三个函数
if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
sockfd:socket 返回的文件描述符.
serv_addr:储存了服务器端的连接信息.其中 sin_add 是服务端的地址
addrlen:serv_addr 的长度
connect 函数是客户端用来同服务端连接的.成功时返回 0,sockfd 是同服务端通讯的文件
描述符 失败时返回-1.
进行客户端程序设计无须调用 bind(),因为这种情况下只需知道目
的机器 的 IP 地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket 执行体
为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到 打断口。
Connect 函数启动和远端主机的直接连接。 只有面向连接的客户程序使用 socket 时才
需要将此 socket 与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从
不启动一个连接,它只是被动的在协议端口监听客户的请求
连接完成后 就可以通行了
-------------------------------------------------------------------------------
回到server
第五个函数
nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用
accept 函数,然后睡眠并等待客户的连接请求。
int accept(int sockfd, void *addr, int *addrlen);
sockfd 是被监听的 socket 描述符,addr 通常是一个指向 sockaddr_in 变量的指针,
该变量用来存放提出连接请求服务的主机的信息(某 台主机从某个端口发出该请求);
addrten 通常为一个指向值为 sizeof(struct sockaddr_in)的整型指针变量。
write函数
syntax:
功能说明:
------------------------------------------
server写后,client需要去读
client中
if(-1 == (recbytes = read(cfd,buffer,1024)))
ssize_t read(int fd,void *buf,size_t nbyte)
函数说明:
通过connect fd已经和服务器取得联系
-----------------------------
函数
close(cfd);没什么问题
------------------------------------------------------------------------------------------------------------
上面是服务器发,客户端收,下面再看看两者相互通信的情况
server_2.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int listenfd, connfd;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
char buff[4096];
int n;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf("create socket error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6660);
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
if( listen(listenfd, 10) == -1){
printf("listen socket error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
printf("======waiting for client's request======\n");
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
exit(0);
}
while(1)
{
n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
printf("send msg to client: \n");
fgets(sendline, 4096, stdin);
if( send(connfd, sendline, strlen(sendline), 0) < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
}
close(listenfd);
}
client_2.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
char buff[4096];
int n;
if( argc != 2){
printf("usage: ./client <ipaddress>/n");
exit(0);
}
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)/n", strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6660);
if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s/n",argv[1]);
exit(0);
}
if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
while(1)
{
printf("send msg to server: \n");
fgets(sendline, 4096, stdin);
if( send(sockfd, sendline, strlen(sendline), 0) < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
n = recv(sockfd, buff, MAXLINE, 0);
buff[n] = '\0';
printf("recv msg from server: %s\n", buff);
}
close(sockfd);
exit(0);
}
下面的使用recv和send两个函数发送的